为啥我在尝试实现 Hibernate 多对多映射时遇到此错误?

Posted

技术标签:

【中文标题】为啥我在尝试实现 Hibernate 多对多映射时遇到此错误?【英文标题】:Why I am obtaining this error trying to implement an Hibernate Many To Many mapping?为什么我在尝试实现 Hibernate 多对多映射时遇到此错误? 【发布时间】:2021-12-20 07:12:44 【问题描述】:

我正在使用 Spring Data Jpa 开发一个 Spring Boot 项目,以便在我的 PostgreSQL 数据库上保存数据。 我发现 Hibernate Many To Many 映射存在一些困难。以下是我的问题的详细信息。在我的项目中,表不是由 Hibernate 创建的,但我使用 Hibernate 定义映射我的表的实体类。

我有这 3 个数据库表:

portal_user 表:存储门户的用户:

 CREATE TABLE IF NOT EXISTS public.portal_user
 (
     id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
     first_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
     middle_name character varying(50) COLLATE pg_catalog."default",
     surname character varying(50) COLLATE pg_catalog."default" NOT NULL,
     sex "char" NOT NULL,
     birthdate date NOT NULL,
     tex_code character varying(50) COLLATE pg_catalog."default" NOT NULL,
     e_mail character varying(50) COLLATE pg_catalog."default" NOT NULL,
     contact_number character varying(50) COLLATE pg_catalog."default" NOT NULL,
     created_at date NOT NULL,
     CONSTRAINT user_pkey PRIMARY KEY (id)
 )

user_type 表:定义可能的用户类型(如 ADMIN、SIMPLE_USER 等):

 CREATE TABLE IF NOT EXISTS public.user_type
 (
     id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
     type character varying(50)[] COLLATE pg_catalog."default" NOT NULL,
     description text COLLATE pg_catalog."default",
     CONSTRAINT user_type_pkey PRIMARY KEY (id)
 )

portal_user_user_type 表:它是 Many To Many 关联表,将前两个表链接在一起:这是因为特定用户可以有多种用户类型和特定用户type 可以关联到多个用户:

 CREATE TABLE IF NOT EXISTS public.portal_user_user_type
 (
     id bigint NOT NULL,
     portal_user_id_fk bigint NOT NULL,
     user_type_id_fk bigint NOT NULL,
     CONSTRAINT portal_user_user_type_pkey PRIMARY KEY (id),
     CONSTRAINT portal_user_user_type_to_portal_user FOREIGN KEY (portal_user_id_fk)
         REFERENCES public.portal_user (id) MATCH SIMPLE
         ON UPDATE NO ACTION
         ON DELETE NO ACTION,
     CONSTRAINT portal_user_user_type_to_user_type FOREIGN KEY (user_type_id_fk)
         REFERENCES public.user_type (id) MATCH SIMPLE
         ON UPDATE NO ACTION
         ON DELETE NO ACTION
 )

好的,那么我已经将前面的 3 个类映射到了以下实体类:

User 类映射 portal_user 表:

@Entity
@Table(name = "portal_user")
@Data
public class User implements Serializable 
     
    private static final long serialVersionUID = 5062673109048808267L;
    
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
    @Column(name = "first_name")
    private String firstName;
    
    @Column(name = "middle_name")
    private String middleName;
    
    @Column(name = "surname")
    private String surname;
    
    @Column(name = "sex")
    private char sex;
    
    @Column(name = "birthdate")
    private Date birthdate;
    
    @Column(name = "tex_code")
    private String taxCode;
    
    @Column(name = "e_mail")
    private String eMail;
    
    @Column(name = "contact_number")
    private String contactNumber;
    
    @Temporal(TemporalType.DATE)
    @Column(name = "created_at")
    private Date createdAt;
    
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
    @JsonManagedReference
    private Set<Address> addressesList = new HashSet<>();
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    @JsonManagedReference
    private Set<User_UserType> userToUserTypeAssociation = new HashSet<>();

    public User(String firstName, String middleName, String surname, char sex, Date birthdate, String taxCode,
            String eMail, String contactNumber, Date createdAt) 
        super();
        this.firstName = firstName;
        this.middleName = middleName;
        this.surname = surname;
        this.sex = sex;
        this.birthdate = birthdate;
        this.taxCode = taxCode;
        this.eMail = eMail;
        this.contactNumber = contactNumber;
        this.createdAt = createdAt;
    
        

注意 1:此 @OneToMany 关系与另一个用例相关并且工作正常(因此它不是此问题的一部分):

@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
@JsonManagedReference
private Set<Address> addressesList = new HashSet<>();

NOTE-2User 类包含此 @OneToMany 关系,它实现了我的 Many To Many 的一侧strong> 导致问题的关系:

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
@JsonManagedReference
private Set<User_UserType> userToUserTypeAssociation = new HashSet<>();

User_UserType 类是映射我的多对多关系的实体类。

然后我有 UserType 实体类映射之前的 user_type 数据库表:

@Entity
@Table(name = "user_type")
@Data
public class UserType implements Serializable 
    
    private static final long serialVersionUID = 6904959949570501298L;

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
    @Column(name = "name")
    private String name;
    
    @Column(name = "description")
    private String description;
    
    @OneToMany(mappedBy = "userType")
    @JsonManagedReference
    private Set<User_UserType> userToUserTypeAssociation = new HashSet<>();
    

    public UserType() 
        super();
        // TODO Auto-generated constructor stub
    
    
    public UserType(String name, String description) 
        super();
        this.name = name;
        this.description = description;
    



注意:这个类包含这个 @OneToMany 关系,代表我正在尝试实现的 多对多 关系的另一端。

@OneToMany(mappedBy = "userType")
@JsonManagedReference
private Set<User_UserType> userToUserTypeAssociation = new HashSet<>();

然后我实现了 User_UserType 实体类映射 portal_user_user_type(我的多对多关联表):

@Entity
@Table(name = "portal_user_user_type")
@Data
public class User_UserType implements Serializable 

    private static final long serialVersionUID = -1334879762781878984L;
    
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
    @ManyToOne()
    @JoinColumn(name = "portal_user_id_fk")
    @JsonBackReference
    private User user;
    
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "user_type_id_fk")
    @JsonBackReference
    private UserType userType;

    public User_UserType() 
        super();
        // TODO Auto-generated constructor stub
    
    
    public User_UserType(User user, UserType userType) 
        super();
        this.user = user;
        this.userType = userType;
    


注意:这个类包含两个@ManyToOne()关系,分别指向相关的UserUserType实例。

最后我尝试通过JUnit测试方法来测试它,这个:

@SpringBootTest()
@ContextConfiguration(classes = GetUserWsApplication.class)
@TestMethodOrder(OrderAnnotation.class)
public class UserRepositoryTest 
    
    @Autowired
    private UsersRepository userRepository;
    
    
    @Test
    @Order(1)
    public void testInsertUser() 
        User user = new User("Mario", null, "Rossi", 'M', new Date(), "XXX", "xxx@gmail.com", "329123456", new Date());
        
        Set<Address> addressesList = new HashSet<>();
        addressesList.add(new Address("Italy", "RM", "00100", "Via XXX 123", "near YYY", user));
        
        user.setAddressesList(addressesList);
        
        Set<UserType> userTypesList = new HashSet<>();
        UserType userType1 = new UserType("ADMIN", "Admin user type !!!");
        UserType userType2 = new UserType("USER", "Just a simple user...");
        
        userTypesList.add(userType1);
        userTypesList.add(userType2);
        
        User_UserType user_UserType1 = new User_UserType(user, userType1);
        User_UserType user_UserType2 = new User_UserType(user, userType2);
        
        Set<User_UserType> user_UserType_List = new HashSet<>();
        user_UserType_List.add(user_UserType1);
        user_UserType_List.add(user_UserType2);
        
        user.setUserToUserTypeAssociation(user_UserType_List);
        
        userRepository.save(user);
        assertTrue(true);
        
    
    

运行此方法 Spring Boot 启动时没有错误消息。在调试模式下运行它到达 save() 方法执行,我试图保存我的 User 实例(期望也保存定义的关系:所以两个在我的关联表中创建了用户类型和关系)。

这里发生了一些奇怪的事情。 save 方法似乎给了我一个异常(我可以在调试模式下看到它),但我在堆栈跟踪中没有收到错误消息。按照我所获得的打印屏幕:

正如您所见,它进入了荧光笔异常,但在堆栈跟踪中将应用程序运行到最后,没有与此异常相关的错误行。这是我从 Spring Boot 启动到结束的整个堆栈跟踪:

 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.6)

2021-11-06 19:09:01.145  INFO 22688 --- [           main] c.e.u.t.R.UserRepositoryTest             : Starting UserRepositoryTest using Java 16.0.1 on ubuntu with PID 22688 (started by andrea in /home/andrea/git/get-user-ws)
2021-11-06 19:09:01.148  INFO 22688 --- [           main] c.e.u.t.R.UserRepositoryTest             : No active profile set, falling back to default profiles: default
2021-11-06 19:09:01.896  INFO 22688 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-11-06 19:09:01.957  INFO 22688 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 53 ms. Found 2 JPA repository interfaces.
2021-11-06 19:09:02.805  INFO 22688 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-11-06 19:09:02.871  INFO 22688 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.32.Final
2021-11-06 19:09:03.071  INFO 22688 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations 5.1.2.Final
2021-11-06 19:09:03.211  INFO 22688 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-11-06 19:09:03.456  INFO 22688 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-11-06 19:09:03.487  INFO 22688 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
2021-11-06 19:09:04.193  INFO 22688 --- [           main] org.hibernate.tuple.PojoInstantiator     : HHH000182: No default (no-argument) constructor for class: com.easydefi.users.entity.User (class must be instantiated by Interceptor)
2021-11-06 19:09:04.323  INFO 22688 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-11-06 19:09:04.330  INFO 22688 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-11-06 19:09:04.553  WARN 22688 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2021-11-06 19:09:05.626  INFO 22688 --- [           main] c.e.u.t.R.UserRepositoryTest             : Started UserRepositoryTest in 4.96 seconds (JVM running for 6.304)
2021-11-06 19:11:09.255  INFO 22688 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2021-11-06 19:11:09.257  INFO 22688 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2021-11-06 19:11:09.264  INFO 22688 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

那么我的代码有什么问题?我错过了什么?我该如何尝试修复它?如果抛出此异常,为什么我没有在堆栈跟踪中获取错误行?

【问题讨论】:

可以在帖子中添加异常类型和异常消息吗? --- 出于好奇:是否有特定原因将连接表显式建模为实体? @Turing85 正如我在帖子中解释的那样,我首先设计了数据库,然后实现了数据库表,最后将这些表映射到实体类上。问题是我在堆栈跟踪中没有得到任何错误文本......那么我怎样才能给你异常值? 预先设计数据库并不意味着我们必须将连接表建模为实体 =) 在UserUserType 中使用@ManyToMany 注释Sets(以及一些伴随的注释) ) 应该足够了。 --- 您可以在运行时从调试器中提取变量的值。当应用程序遇到断点时,您应该能够切换到调试视图并查看当前帧中的所有变量。从那里,您可以访问Throwable 的确切类型以及所有字段(例如异常消息)。 【参考方案1】:

portal_user_user_type 中的 id 应该是自动生成的,但它是 id bigint NOT NULL

portal_user_user_type 仅包含对UserUserType 的引用。因此,您可以轻松地简化模型。

类似:

class User 
    
    @ManyToMany(cascade =  CascadeType.ALL )
    @JoinTable(
        name = "portal_user_user_type", 
        joinColumns =  @JoinColumn(name = "portal_user_id_fk") , 
        inverseJoinColumns =  @JoinColumn(name = "user_type_id_fk") 
    )
    Set<UserType> userTypes;
    

【讨论】:

是的,ID 是自动生成的大整数 NOT NULL。按照您的建议执行此操作以与我的数据库表上的 ID 不能为 NULL 的事实相关的另一个错误结束:“错误:关系“portal_user_user_type”的“id”列中的空值违反非空约束”跨度> 使用此解决方案您不需要 portal_user_user_type 中的 ID 是的,我知道我可以简单地创建一个包含我的两个 FK 的复合键......但由于某些原因,我不想拥有一个单独的 ID PK。那么使用这个@ManyToMany注解是不可能的吗? Tnx 我猜你可以使用'id bigint NOT NULL GENERATED ALWAYS AS IDENTITY.....'而不在你的实体中映射它......但这有点没用......

以上是关于为啥我在尝试实现 Hibernate 多对多映射时遇到此错误?的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate hbm 多对多映射

Hibernate学习笔记 --- 创建基于中间关联表的多对多映射关系

框架 day32 Hibernate,一级缓存,关联关系映射(一对多,多对多)

hibernate 表关系映射详解之多对多

hibernate的映射之四(多对多双向关联)

hibernate的映射之三(多对多单向关联)