为啥我在尝试实现 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-2:User 类包含此 @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()关系,分别指向相关的User和UserType实例。
最后我尝试通过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 正如我在帖子中解释的那样,我首先设计了数据库,然后实现了数据库表,最后将这些表映射到实体类上。问题是我在堆栈跟踪中没有得到任何错误文本......那么我怎样才能给你异常值? 预先设计数据库并不意味着我们必须将连接表建模为实体 =) 在User
和UserType
中使用@ManyToMany
注释Set
s(以及一些伴随的注释) ) 应该足够了。 --- 您可以在运行时从调试器中提取变量的值。当应用程序遇到断点时,您应该能够切换到调试视图并查看当前帧中的所有变量。从那里,您可以访问Throwable
的确切类型以及所有字段(例如异常消息)。
【参考方案1】:
portal_user_user_type
中的 id 应该是自动生成的,但它是 id bigint NOT NULL
?
但portal_user_user_type
仅包含对User
和UserType
的引用。因此,您可以轻松地简化模型。
类似:
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学习笔记 --- 创建基于中间关联表的多对多映射关系