解决Spring使用@ManyToMany注解后报错,显示该表doesn‘t exist

在SpringBoot项目中,从H2数据库切换到MySQL时,使用@ManyToMany注解遇到表不存在的错误。问题源于中间表Taco_Ingredient未创建。解决方案是使用@JoinTable注解显式定义多对多关系的中间表,包括表名和两个外键的关联列名。

项目场景:

项目语言:Java,框架:Spring Boot,书籍:Spring 实战(第5版)

在学习Spring 实战(第5版)
该书使用h2数据库进行教学,中途嫌麻烦想换成mysql进行余下的学习,切换中发生错误


问题描述

使用@ManyToMany注解的时候报错

报错信息

2023-04-23 23:24:54.131 ERROR 19164 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statement] with root cause

java.sql.SQLSyntaxErrorException: Table 'taco_cloud.Taco_Ingredient' doesn't exist
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120) ~[mysql-connector-java-8.0.21.jar:8.0.21]
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.21.jar:8.0.21]
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.21.jar:8.0.21]

源代码

package tacos.domain;

import lombok.Data;
import lombok.NonNull;

import javax.persistence.*;
import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;

@Data
@Entity
public class Taco {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NonNull
    @Size(min = 5, message = "名字的长度必须大于5个字符")
    private String name;

    @Size(min = 1, message = "你必须得选择一项食材")
    @ManyToMany(targetEntity = Ingredient.class)
    private List<Ingredient> ingredients;

    private Date createdAt;

    @PrePersist
    private void createAt() {
        this.createdAt = new Date();
    }

    public Taco() {

    }
}


原因分析:

报错显示 Table 'taco_cloud.Taco_Ingredient' doesn't exist
说明是表不存在的问题,可以选择建立对应的表,但是这样太局限

例如:Handler 发送消息有两种方式,分别是 Handler.obtainMessage()Handler.sendMessage(),其中 obtainMessage 方式当数据量过大时,由于 MessageQuene 大小也有限,所以当 message 处理不及时时,会造成先传的数据被覆盖,进而导致数据丢失。


解决方案:

所以选择使用@JoinTable注解为@ManyToMany建立表关系映射

	@ManyToMany(targetEntity = Ingredient.class)
    @JoinTable(
            name = "Taco_Ingredients",
            joinColumns = {@JoinColumn(name = "taco", referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "ingredient", referencedColumnName = "id")}
    )
    private List<Ingredient> ingredients;

映射的注解说明

@ManyToMany

  • 作用:用于映射多对多关系
  • 属性:
    • cascade:配置级联操作。
    • fetch:配置是否采用延迟加载。
    • targetEntity:配置目标的实体类。

@JoinTable

  • 作用:针对中间表的配置
  • 属性:
    • name:配置中间表的名称
    • joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
    • inverseJoinColumn:中间表的外键字段关联对方表的主键字段

<think>我们正在解决的是:JPA Entity使用@Data注解,因使用关联实体导致toString报错的问题。 问题分析: 当我们使用Lombok的@Data注解时,它会自动生成toString()方法。如果实体类中有关联关系(如@OneToMany, @ManyToOne等), 那么toString()方法会尝试输出关联对象。如果关联对象本身又引用了原始对象(双向关联),则会导致循环调用,最终栈溢出(StackOverflowError)。 引用[1]和[2]提到了JPA实体之间的关联关系(双向关联)容易引发问题,而引用[3]和[4]则提到了实体注解和查询的使用解决方法: 1. **避免在toString中包含关联实体**(推荐) 手动重写toString()方法,排除关联字段。由于使用了@Data,我们可以通过显式重写toString()来覆盖Lombok自动生成的。 2. **使用Lombok的@ToString注解进行定制** 使用@ToString(exclude = "关联字段名")来排除关联字段。 3. **使用JSON注解忽略关联字段** 在关联字段上使用@JsonIgnore,但这主要用于序列化,对于toString()无效。所以我们采用。 4. **使用工具类避免循环** 使用第三方工具(如Apache Commons Lang3的ToStringBuilder)并设置排除字段。 下面详细说明前两种方法: ### 方法1:手动重写toString() 例如,有两个实体:Order(订单)和OrderItem(订单项),双向关联。 ```java @Entity @Data public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String orderNumber; @OneToMany(mappedBy = "order") private List<OrderItem> items; // 需要排除的关联集合 // 手动重写toString,排除items字段 @Override public String toString() { return "Order{" + "id=" + id + ", orderNumber='" + orderNumber + '\'' + '}'; } } ``` ```java @Entity @Data public class OrderItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String productName; @ManyToOne @JoinColumn(name = "order_id") private Order order; // 关联的Order // 同样重写toString,排除order字段 @Override public String toString() { return "OrderItem{" + "id=" + id + ", productName='" + productName + '\'' + '}'; } } ``` ### 方法2:使用Lombok的@ToString注解排除字段 使用Lombok的@ToString注解,在类上指定排除的字段。 ```java @Entity @Data @ToString(exclude = "items") // 排除items字段 public class Order { // ...同上 } ``` ```java @Entity @Data @ToString(exclude = "order") // 排除order字段 public class OrderItem { // ...同上 } ``` 注意:在双向关联中,双方都需要排除对方的引用字段。 ### 注意事项 - 循环引用问题仅出现在toString(),也可能出现在hashCode()和equals()方法中。因此,建议同时检查这些方法,避免包含关联字段。 - 解决方案:使用@EqualsAndHashCode(exclude = "关联字段")来排除关联字段,或者手动重写hashCode和equals方法。 例子: ```java @Entity @Data @ToString(exclude = "items") @EqualsAndHashCode(exclude = "items") // 同时排除items字段在equals和hashCode中的使用 public class Order { // ...同上 } ``` 为什么需要排除?因为如果在hashCode或equals中使用了关联字段(特别是集合),那么当关联集合发生变化时,hashCode会改变,这样在使用HashSet或HashMap时会出现问题。 总结步骤: 1. 在双向关联的实体中,使用@ToString(exclude)排除对方的引用字段(或者手动重写toString)。 2. 同样,使用@EqualsAndHashCode(exclude)排除对方的引用字段(或者手动重写hashCode和equals)。 这样就能有效避免循环引用导致的栈溢出问题。 相关问题: 1. 如何解决JPA双向关联导致的JSON序列化循环引用问题? 2. 在JPA实体中,如何正确实现equals和hashCode方法? 3. Lombok的@Data注解在JPA实体中使用时需要注意什么? 4. 除了排除关联字段,还有哪些方法可以避免JPA实体toString()的循环引用? 引用说明: 我们在解决toString循环引用问题时,参考了JPA实体关联关系的特性[^2]和实体注解使用[^3]。 </think>以下是解决 JPA Entity 使用 `@Data` 注解时因关联实体导致 `toString()` 报错的完整方案: ### 问题原因分析 当使用 Lombok 的 `@Data` 注解时,会自动生成包含**所有字段**的 `toString()` 方法。若实体存在**双向关联**(如 `@OneToMany` 和 `@ManyToOne`),会触发循环调用: ```java @Entity @Data // 问题根源:自动生成危险的toString() public class Order { @OneToMany(mappedBy = "order") private List<OrderItem> items; // 关联对象 } @Entity @Data public class OrderItem { @ManyToOne private Order order; // 反向引用 } ``` 当调用 `order.toString()` 时: 1. 尝试输出 `items` 字段 2. `OrderItem.toString()` 尝试输出 `order` 字段 3. `order.toString()` 再次被触发 → **无限递归 → StackOverflowError**[^1][^2] --- ### 解决方案 #### ✅ 方法1:排除关联字段(推荐) 使用 Lombok 的 `@ToString(exclude)` 显式排除关联字段: ```java @Entity @Data @ToString(exclude = "items") // ✅ 排除双向关联字段 public class Order { @OneToMany(mappedBy = "order") private List<OrderItem> items; } @Entity @Data @ToString(exclude = "order") // ✅ 反向关联也需排除 public class OrderItem { @ManyToOne private Order order; } ``` #### ✅ 方法2:手动重写 toString() 完全自定义输出逻辑,避免关联字段: ```java @Entity @Data public class Order { @OneToMany(mappedBy = "order") private List<OrderItem> items; @Override public String toString() { return "Order(id=" + id + ", name=" + name + ")"; // ✅ 仅输出基本字段 } } ``` #### ✅ 方法3:使用工具类安全输出 引入 `ToStringBuilder` 避免递归: ```java import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @Entity @Data public class Order { @OneToMany(mappedBy = "order") private List<OrderItem> items; @Override public String toString() { return ToStringBuilder.reflectionToString( this, ToStringStyle.SHORT_PREFIX_STYLE, false, // ✅ 关键:跳过关联对象 null ); } } ``` --- ### 关联字段的通用处理规范 | 注解类型 | 处理建议 | 示例 | |----------------|-----------------------------------|--------------------------| | `@OneToMany` | 必须排除集合字段 | `@ToString(exclude = "items")` | | `@ManyToOne` | 建议排除引用字段 | `@ToString(exclude = "order")` | | `@ManyToMany` | 双向关联双方均需排除 | 双方都添加 `exclude` | | `@OneToOne` | 双向关联需排除对方引用 | 相互 `exclude` | --- ### 扩展注意事项 1. **equals()/hashCode() 风险** `@Data` 同样会生成包含关联字段的 `equals/hashCode`,可能导致性能问题或循环调用。解决方案: ```java @Data @EqualsAndHashCode(exclude = {"items", "order"}) // ✅ 双重防护 ``` 2. **JSON 序列化陷阱** 即使修复 `toString()`,Jackson 序列化仍可能触发循环引用。需追加注解: ```java @OneToMany(mappedBy = "order") @JsonIgnore // ✅ 阻断JSON序列化链路[^4] private List<OrderItem> items; ``` 3. **双向关联维护建议** 在 JPA 中正确设置关联关系可降低风险: ```java public void addItem(OrderItem item) { items.add(item); item.setOrder(this); // ✅ 同步维护双向关联 } ``` --- ### 紧急调试技巧 若在调试时意外触发循环调用: 1. 立即暂停调试 2. 在 IDEA 的变量窗口右键关联对象 → **"Create Custom Data View..."** 3. 输入过滤达式:`{ id: id, name: name }`(仅展示安全字段) > 通过以上方案,可彻底解决因 `@Data` 自动生成的 `toString()` 在 JPA 双向关联中引发的递归问题。优先推荐 **`@ToString(exclude)` + `@EqualsAndHashCode(exclude)` 组合方案**。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值