I have an application where I am trying to save an object into a database. I know the object contains the data as i can see it in the logs but the application fails because the cc_cvv is being inserted as null
SQL Error: 23502, SQLState: 23502
NULL not allowed for column “CC_CVV”;
Order Submitted: TacoOrder(id=1, placedAt=Thu Sep 26 04:44:33 BST
2024, deliveryName=Tosin, deliveryStreet=Cable Walk,
deliveryCity=London, deliveryZip=SE1 0TN, deliveryState=Greater
London, ccNumber=4462192391239123, ccExpiration=11/34, ccCVV=111,
tacos=[Taco(id=2, createdAt=Thu Sep 26 04:44:33 BST 2024,
name=asdasdasd, ingredients=[Ingredient(id=FLTO, name=Flour Tortilla,
type=WRAP), Ingredient(id=GRBF, name=Ground Beef, type=PROTEIN),
Ingredient(id=CHED, name=Cheddar, type=CHEESE), Ingredient(id=LETC,
name=Lettuce, type=VEGGIES), Ingredient(id=SRCR, name=Sour Cream,
type=SAUCE)])])
And below we can see the attempt to insert the data into the database
insert
into
taco_order
(cccvv, cc_expiration, cc_number, delivery_city, delivery_name, delivery_state, delivery_street, delivery_zip,
placed_at, id)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 2024-09-26 04:48:13.454 TRACE 36200 — [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder :
binding parameter [1] as [VARCHAR] – [111] 2024-09-26 04:48:13.454
TRACE 36200 — [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder
: binding parameter [2] as [VARCHAR] – [11/34] 2024-09-26 04:48:13.456
TRACE 36200 — [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder
: binding parameter [3] as [VARCHAR] – [4462192391239123] 2024-09-26
04:48:13.456 TRACE 36200 — [nio-8080-exec-3]
o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as
[VARCHAR] – [London] 2024-09-26 04:48:13.456 TRACE 36200 —
[nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder : binding
parameter [5] as [VARCHAR] – [TaciBoi] 2024-09-26 04:48:13.456 TRACE
36200 — [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder :
binding parameter [6] as [VARCHAR] – [Greater London] 2024-09-26
04:48:13.456 TRACE 36200 — [nio-8080-exec-3]
o.h.type.descriptor.sql.BasicBinder : binding parameter [7] as
[VARCHAR] – [Cable Drive] 2024-09-26 04:48:13.456 TRACE 36200 —
[nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder : binding
parameter [8] as [VARCHAR] – [SE1 0GA] 2024-09-26 04:48:13.456 TRACE
36200 — [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder :
binding parameter [9] as [TIMESTAMP] – [Thu Sep 26 04:47:36 BST 2024]
2024-09-26 04:48:13.457 TRACE 36200 — [nio-8080-exec-3]
o.h.type.descriptor.sql.BasicBinder : binding parameter [10] as
[BIGINT] – [3]
My classes
Order Controller
package com.SpringInAction.Tacos.controllers;
import com.SpringInAction.Tacos.models.TacoOrder;
import com.SpringInAction.Tacos.repositories.OrderRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import javax.validation.Valid;
@Slf4j
@Controller
@RequestMapping("/orders")
@SessionAttributes("tacoOrder")
public class OrderController {
private OrderRepository orderRepository;
// @Autowired
public OrderController(OrderRepository orderRepository){
this.orderRepository = orderRepository;
}
@GetMapping("/current")
public String orderForm(){
return "orderForm";
}
/**
* Post Mapping is automatically picked up after order form is submitted
* A complete status indicates that the session details can now be cleaned up
* @param order
* @param status
* @return
*/
@PostMapping
@Transactional
public String processOrder(@Valid TacoOrder order,
Errors errors,
SessionStatus status){
if(errors.hasErrors()){
return "orderForm";
}
orderRepository.save(order);
log.info("Order Submitted: {}" , order);
status.setComplete();
return "redirect:/";
// return "redirect:/";
}
}
Order Repository
package com.SpringInAction.Tacos.repositories;
import com.SpringInAction.Tacos.models.TacoOrder;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
* Allows for tacoOrder object to persist
* and because taco is stored inside tacoOrder
* taco will also persist
*/
@Repository
public interface OrderRepository extends JpaRepository<TacoOrder, Long> {
// TacoOrder save(TacoOrder order);
}
Taco Class
package com.SpringInAction.Tacos.models;
import lombok.Data;
import lombok.Generated;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Data //create java bean methods on compile time
@Entity
public class Taco {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Date createdAt;
@NotNull
@Size(min = 5, message = "Name must be at least 5 characters long")
private String name;
@NotNull
@ManyToMany() //TODO adding this causes Error -: Error executing DDL "alter table taco_ingredients
@Size(min = 1, message = "You must choose at least 1 ingredient")
private List<Ingredient> ingredients = new ArrayList<>();
public void addIngredients(Ingredient ingredient){
ingredients.add(ingredient);
}
}
Taco Order Class
package com.SpringInAction.Tacos.models;
import com.SpringInAction.Tacos.models.Taco;
import lombok.Data;
import javax.persistence.*;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Tacoorder will be stored inside the http session
* In order for data to be transfered across JVM and be stored after server restart
* Object must be first serialised
*/
@Data
@Entity
public class TacoOrder implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Date placedAt;
@NotBlank(message="Delivery name is required")
private String deliveryName;
@NotBlank(message="Street name is required")
private String deliveryStreet;
@NotBlank(message="City is required")
private String deliveryCity;
@NotBlank(message="Zip Code is is required")
private String deliveryZip;
@NotBlank(message="Delivery State is required")
private String deliveryState;
@NotBlank(message="Provide Valid CC Name")
private String ccNumber;
@Pattern(regexp="^(0[1-9]|1[0-2])([\/])([2-9][0-9])$", message="Must be formatted MM/YY")
private String ccExpiration;
@Digits(integer=3, fraction=0, message="Invalid CVV")
private String ccCVV;
@OneToMany(cascade = CascadeType.ALL) //cascade deleted taco elements if order is deleted. One taco to may taco orders
private List<Taco> tacos = new ArrayList<>();
public void addTaco(Taco taco){
tacos.add(taco);
}
}
2