You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA插入Ticket报错:Column 'seller' cannot be null问题求助

问题描述

需要向SQL数据库添加新的Ticket记录,Ticket以event和code作为复合主键。已创建带复合主键的实体类,但执行POST请求时始终报错**"Column 'seller' cannot be null"**。尝试过IdClass和Embeddable两种实现复合主键的方式,均出现错误;GET请求可正常工作,但插入新对象的操作一直失败。对Java Persistence概念理解不够深入,暂无时间系统学习,希望得到概念解释与解决方案。


实体类

Event.java

@Entity
@Table(name="events")
@NamedQuery(name="Event.findAll", query="SELECT e FROM Event e")
public class Event implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private long id;

    private String category;

    private String city;

    @Temporal(TemporalType.DATE)
    private Date date;

    @Column(name="image_path")
    private String imagePath;

    private String name;

    private String venue;

    //bi-directional one-to-many association to Ticket
    @OneToMany(mappedBy="eventBean")
    @JsonManagedReference(value="eventBean")
    private List<Ticket> tickets;

    public Event() {
    }

Ticket.java

@Entity
@Table(name="tickets")
@IdClass(TicketPK.class)
//@NamedQuery(name="Ticket.findAll", query="SELECT t FROM Ticket t")
public class Ticket implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    private long code;
    
    @Id
    private long event;

    private float price;

    private String type;
    
    //bi-directional many-to-one association to Event
    //One Event can have many tickets
    @ManyToOne(fetch = FetchType.EAGER)
    @JsonBackReference(value="eventBean")
    @JoinColumn(name="event",nullable = false,insertable=false,updatable=false)
    private Event eventBean;

    //bi-directional many-to-one association to User
    //One User can have many tickets
    @ManyToOne(fetch = FetchType.LAZY)
    @JsonBackReference(value="seller")
    @JoinColumn(name="seller", referencedColumnName = "alias")
    private User user;

    public Ticket() {
    }
    
    public Ticket(long event, float price, Event eventBean, User user) {
        this.event = event;
        this.price = price;
        this.eventBean = eventBean;
        this.user = user;
    }

   //getters and setters

TicketPK.java

public class TicketPK implements Serializable {
    //default serial version id, required for serializable classes.
    private static final long serialVersionUID = 1L;

    private long code;

    private long event;

    public TicketPK() {
    }
    
    public TicketPK(long code, long event) {
        this.code = code;
        this.event = event;
    }
    
//getters and setters

    public boolean equals(Object other) {
        if (this.equals(other)) {
            return true;
        }
        if (!(other instanceof TicketPK)) {
            return false;
        }
        TicketPK castOther = (TicketPK)other;
        return 
            (this.code == castOther.code)
            && (this.event == castOther.event);
    }

    public int hashCode() {
        final int prime = 31;
        int hash = 17;
        hash = hash * prime + ((int) (this.code ^ (this.code >>> 32)));
        hash = hash * prime + ((int) (this.event ^ (this.event >>> 32)));
        
        return hash;
    }

}

User.java

@Entity
@Table(name="users")
@NamedQuery(name="User.findAll", query="SELECT u FROM User u")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private String alias;

    private String address;

    @Column(name="admin_status")
    private byte adminStatus;

    private String name;

    private String password;

    @Column(name="phone_number")
    private int phoneNumber;

    //bi-directional one-to-many association to Ticket
    //One User can have many Tickets
    @OneToMany(mappedBy="user")
    @JsonManagedReference(value="seller")
    private List<Ticket> tickets;

    public User() {
    }

DAO

TicketDAO

public interface TicketDAO extends CrudRepository<Ticket, TicketPK>{

    public List<Ticket> findAll();
}

控制器

@Controller
@CrossOrigin
public class MyController {

    @Autowired
    TicketDAO daoticket;
    
    /****************** OPERATIONS ON Tickets ************************************/

    
    @RequestMapping(value="/tickets", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Ticket> createTicket(@RequestBody Ticket ticket) {
        
        ResponseEntity<Ticket> response;
        
        Ticket newTicket = daoticket.save(ticket);
        if (newTicket == null) {
            response = new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
        } else {
            response = new ResponseEntity<>(newTicket, HttpStatus.CREATED);
        }
        return response;    
    }
}

SQL数据库

CREATE DATABASE IF NOT EXISTS `BD89_03_ticketsell`;
USE `BD89_03_ticketsell`;

DROP TABLE IF EXISTS `TICKETS`;
DROP TABLE IF EXISTS `EVENTS`;
DROP TABLE IF EXISTS `USERS`;

CREATE TABLE `users` (
    `name` varchar(100),
    `alias` varchar(100) NOT NULL UNIQUE,
    `password` varchar(100) NOT NULL,
    `address` varchar(100),
    `phone_number` int(10),
    `admin_status` boolean NOT NULL,
    PRIMARY KEY(`alias`)
);

CREATE TABLE `events`(
    `id` bigint NOT NULL UNIQUE AUTO_INCREMENT,
    `name` varchar(100) NOT NULL,
    `category` varchar(100) NOT NULL,
    `date` date NOT NULL,
    `city`varchar(100) NOT NULL,
    `venue` varchar(100) NOT NULL,
    `image_path` varchar(300) NOT NULL,
    PRIMARY KEY(`id`)
);


CREATE TABLE `tickets`(
    `code` bigint NOT NULL,
    `event` bigint NOT NULL,
    `type` varchar(100),
    `price` FLOAT(10,2) NOT NULL,
    `seller` varchar(100) NOT NULL,
    PRIMARY KEY(`code`, `event`),
    FOREIGN KEY(`seller`) REFERENCES `users`(`alias`),
    FOREIGN KEY(`event`) REFERENCES `events`(`id`)
);

INSERT INTO events (`name`, `category`, `date`, `city`, `venue`, `image_path`)
VALUES ('Cut Cupy', 'indie dance', '2024-01-22', 'Madrid', 'La Riviera', 'images/CUT-COPY2.png'),
('Dub Inc Tour 2022', 'reggae', '2021-05-03', 'Madrid', 'Sala Sol', 'images/DUB-INC-TOUR.jpg'),
('Kodaline', 'folk', '2022-03-15', 'Sevilla', 'Tablao el gitanillo', 'images/Kodaline.png');

INSERT INTO users (`name`,`alias`,`password`,`address`,`phone_number`, `admin_status`)
VALUES ('pepe', 'pepe', 'password', '17 Calle', '628791051', TRUE),
('pepe2', 'pepe2', 'password', '0 Calle', '628791052', FALSE),
('elisa', 'elisacool', 'contraseña', '3 Calle', '628791053', FALSE);

INSERT INTO tickets (`code`,`event`,`type`,`price`,`seller`)
VALUES ('1234', '1', 'palco', '17.5', 'pepe'),
('666666', '3', 'silla', '20', 'elisacool'),
('666666', '1', 'anfiteatro', '12', 'pepe2'),
('222222', '2', 'palco 3', '21.47', 'elisacool'),
('444666', '3', 'plató', '5', 'pepe'),
('111111', '2', 'palco 2', '40.99', 'elisacool');

控制台错误

2022-12-06 15:52:46.186  WARN 9656 --- [io-10303-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1048, SQLState: 23000
2022-12-06 15:52:46.186 ERROR 9656 --- [io-10303-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : Column 'seller' cannot be null
2022-12-06 15:52:46.186  INFO 9656 --- [io-10303-exec-2] o.h.e.j.b.internal.AbstractBatchImpl     : HHH000010: On release of batch it still contained JDBC statements
2022-12-06 15:52:46.199 ERROR 9656 --- [io-10303-exec-2] 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.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause


java.sql.SQLIntegrityConstraintViolationException: Column 'seller' cannot be null
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-j-8.0.31.jar:8.0.31]
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-j-8.0.31.jar:8.0.31]
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:916) ~[mysql-connector-j-8.0.31.jar:8.0.31]
    ...

原因分析与解决方案

核心问题

报错直接原因是数据库tickets表的seller字段被设为NOT NULL,但插入时该字段值为null。深层原因是JPA映射和请求数据绑定的双重问题:

  1. Ticket实体中关联User的字段user未明确标记非空,且请求未传递有效的seller信息,导致JPA无法生成合法的seller值。
  2. 复合主键的equals方法存在递归错误,可能影响实体的持久化判断。

关键概念速览

  • 复合主键:由多个字段共同组成的主键,JPA支持两种实现方式:
    • @IdClass:需要独立的主键类(如TicketPK),实体类用@Id标注每个主键字段。
    • @EmbeddedId:将主键字段封装在一个可嵌入类中,实体类用@EmbeddedId标注该类实例。
  • 关联映射:@ManyToOne用于多对一关联,@JoinColumn指定数据库外键列;insertable=false, updatable=false表示该字段不由JPA负责插入/更新,适用于主键同时作为外键的场景。

具体修复步骤

1. 修正Ticket实体的User关联映射

给user字段添加nullable=false约束,确保JPA强制该字段非空:

@ManyToOne(fetch = FetchType.LAZY)
@JsonBackReference(value="seller")
@JoinColumn(name="seller", referencedColumnName = "alias", nullable = false)
private User user;

2. 修复TicketPK的equals方法逻辑

原方法存在无限递归问题,修改为直接比较引用和字段值:

public boolean equals(Object other) {
    if (this == other) {
        return true;
    }
    if (!(other instanceof TicketPK)) {
        return false;
    }
    TicketPK castOther = (TicketPK)other;
    return this.code == castOther.code && this.event == castOther.event;
}

3. 确保POST请求传递有效seller信息

请求JSON需包含User的主键alias,示例请求体:

{
  "code": 777777,
  "event": 1,
  "price": 25.0,
  "type": "general",
  "user": {"alias": "pepe"}
}

4. 控制器中验证并关联持久化User对象

保存Ticket前,从数据库查询对应的User,确保关联的是持久化对象:

@Autowired
UserDAO daoUser; // 需创建继承CrudRepository<User, String>的UserDAO接口

@RequestMapping(value="/tickets", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Ticket> createTicket(@RequestBody Ticket ticket) {
    // 验证seller信息是否存在
    if (ticket.getUser() == null || ticket.getUser().getAlias() == null) {
        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
    }
    // 查询数据库中存在的User
    User existingUser = daoUser.findById(ticket.getUser().getAlias()).orElse(null);
    if (existingUser == null) {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
    ticket.setUser(existingUser);
    Ticket newTicket = daoticket.save(ticket);
    return new ResponseEntity<>(newTicket, HttpStatus.CREATED);
}

验证修复

  1. 确认数据库中存在对应alias的User记录(如pepe)。
  2. 发送符合格式的POST请求,检查是否成功插入Ticket记录。

内容的提问来源于stack exchange,提问作者B0naparte

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 02:20:27