JPA ManyToMany fails with "must have same number of columns as the referenced primary key" - spring-boot

I have three tables customer, product and sales. sales is the join table to store the customer's products as shown below:
customer, product and Sales tables
My Entities defined as described below:
Customer.java
#Entity
#Table(name="customer")
public class Customer {
#Id
#Column(name="c_id")
private String customerId;
#Column(name="customer_name")
private String customerName;
#ManyToMany
#JoinTable(
name = "sale",
joinColumns = #JoinColumn(name = "c_id"),
inverseJoinColumns = #JoinColumn(name = "p_id"))
private Set<Product> customerProducts = new HashSet<>();
}
Product.java
#Entity
#Table(name="product")
public class Product {
#Id
#Column(name="p_id")
private String productId;
#Column(name="product_name")
private String productName;
#Column(name="price")
private Double price;
// ... Setters & Getters
}
Sales.java
#Entity
#Table(name="sales")
public class Sales {
#EmbeddedId
private SalesPK salesId;
#Column(name="qty")
private Long qty;
// ... Setters & Getters
}
SalesPK.java
#Embeddable
public class SalesPK implements Serializable {
#Column(name = "c_id")
private String customerId;
#Column(name = "p_id")
private String productId;
public SalesPK() {}
public SalesPK(String customerId, String productId) {
this.customerId = customerId;
this.productId = productId;
}
}
CustomerRepository.java
#Repository
public interface CustomerRepository extends CrudRepository<Customer, String> {
#Query("select customer from Customer customer " +
"left join fetch customer.customerProducts " +
"where customer.customerName = :customerName")
public Customer getCustomerPurchasedProducts(String customerName);
}
My Spring boot application fail to start with following exception:
org.hibernate.MappingException: Foreign key (FK7wwx8x75009xqb1y0tawm8rty:SALES [p_id])) must have same number of
columns as the referenced primary key (SALES [c_id,p_id])
What am I missing here? I have followed the notes as described here in https://www.baeldung.com/jpa-many-to-many
UPDATE:
There is no issue with above solution, I have misspelled "sales" table in #ManyToMany declaration changing from "sale" to "sales" fixed the issue. Strange behavior why it didn't compline about missing table instead it complain about actual composite primary key definition.
Following code Fixed the issue:
#ManyToMany
#JoinTable(
name = "sales",
joinColumns = #JoinColumn(name = "c_id"),
inverseJoinColumns = #JoinColumn(name = "p_id"))
private Set<Product> customerProducts = new HashSet<>();
}

I would map these classes a bit differently:
#Entity
#Table(name="customer")
public class Customer {
#Id
#Column(name="c_id")
private String customerId;
#Column(name="customer_name")
private String customerName;
#OneToMany(mappedBy = "customer")
private Set<Sale> customerSales = new HashSet<>();
}
#Entity
#Table(name="product")
public class Product {
#Id
#Column(name="p_id")
private String productId;
#Column(name="product_name")
private String productName;
#Column(name="price")
private Double price;
}
#Entity
#Table(name="sales")
public class Sales {
#EmbeddedId
private SalesPK salesId;
#MapsId("customerId") // maps customerId attribute of embedded id
#ManyToOne
Customer customer;
#MapsId("productId") // maps productId attribute of embedded id
#ManyToOne
Product product;
#Column(name="qty")
private Long qty;
// ... Setters & Getters
}
#Embeddable
public class SalesPK implements Serializable {
#Column(name = "c_id")
private String customerId;
#Column(name = "p_id")
private String productId;
public SalesPK() {}
public SalesPK(String customerId, String productId) {
this.customerId = customerId;
this.productId = productId;
}
}

Related

Can I get the list of objects in relation #OneToMany?

I've got 2 classes: Device and Category. 1 Device can have 1 assigned category, but 1 category can have assigned many different devices.
#Entity
#Data
#Table(name = "devices")
public class Device implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#Column(name="amount_of_items")
private Integer amountOfItems;
private BigDecimal price;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "category_id")
private Category category;
public Device(String name, String description, Integer amountOfItems, BigDecimal price, Category category){
this.name = name;
this.description = description;
this.amountOfItems = amountOfItems;
this.price = price;
this.category = category;
}
public Device() {}
}
#Entity
#Data
#Table(name = "categories")
public class Category implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#OneToMany(mappedBy = "category", fetch = FetchType.EAGER)
private List<Device> devices = new ArrayList<>();
public Category(String name, String description){
this.name = name;
this.description = description;
}
public Category() { }
}
Can I get the actual list of devices for one Category? The below code returns me a null list of devices:
Category category = new Category("Urzadzenia AGD", "tylko dla klientow premium");
categoryRepository.save(category);
Device device = new Device("pralka", "samoobslugowa", 50, new BigDecimal("220"),
category);
deviceRepository.save(device);
System.out.println(category.getDevies()) ---> returns NULL
Can I do it by calling a getter like above?
save method already return value after save in Database you can use this
Category category = new Category("Urzadzenia AGD", "tylko dla klientow premium");
category= categoryRepository.save(category);
Device device = new Device("pralka", "samoobslugowa", 50, new BigDecimal("220"),
category);
deviceRepository.save(device);
System.out.println(category.getDevies())
and you must be make setter and getter method in your class
after this you have problem stackover flow exciption becouse the all device called category and categore call Devices
you can used #JsonIgnore annotation
Like this :
#OneToMany(mappedBy = "category", fetch = FetchType.EAGER)
#JsonIgnore
private List<Device> devices = new ArrayList<>();

Shared Primary Key between two Entities Not Working

I have created two Entities namely Teacher and Detail, the code snippet is shown below
Teacher.java
#Entity
#Table(name = "teacher")
public class Teacher implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#Column(name = "age")
private int age;
#OneToOne(mappedBy = "teacher", cascade = CascadeType.ALL)
private Detail detail;
public Teacher() {
}
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
//getter and setter
}
Detail.java
#Entity
#Table(name = "detail")
public class Detail implements Serializable {
#Id
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id")
private Teacher teacher;
#Column(name = "subjects")
private String subjects;
public Detail() {
}
public Detail(String subjects) {
this.subjects = subjects;
}
//getter and setter
}
I am trying to achieve one to one mapping with the shared primary key concept
but when i execute the controller, only Teacher table is updating with the value
try {
Teacher teacher=new Teacher("xyz",23);
Detail detail=new Detail("Java,c,c++");
teacher.setDetail(detail);
session.beginTransaction();
session.save(teacher);
session.getTransaction().commit();
model.addAttribute("added", "data inserted");
session.close();
}
After executing only Teacher table is updated with the specified values.Detail table is still showing empty
It does not work exactly like that. You still need the id field in your Detail, so add:
#Id
private long id;
to your Deatail class.
And - as comment suggests - replace the #Id annotation in field Teacher to #MapsId. This way the id of Teacher is mapped to the id of Detail BUT ONLY if you also set the teacher to the detail - you always need to set both sides of relationship - like:
teacher.setDetail(detail);
detail.setTeacher(teacher);

Spring Boot : Error :Cannot call sendError() after the response has been committed

I am getting this error .
Cannot call sendError() after the response has been committed
Can someone help me figure out why?.
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToOne(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL
)
#JoinColumn(name = "details_id")
private Details details;
//Getters and setters left out for brevity
}
#Entity
public class Details {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String description;
private float price;
private float discount;
#OneToOne(mappedBy = "details")
private Product product;
}
#RestController
public class ProductController {
#Autowired
ProductRepository productRepository;
#GetMapping("/getAllProducts")
public Iterable<Product> getAllProducts(){
return productRepository.findAll();
}
}
#RestController
public class DetialsController {
#Autowired
ProductRepository productRepository;
#Autowired
DetailsRepository detailsRepository;
#PostMapping("/details")
public Details addDetails(#RequestBody Details details) {
Product newProduct = new Product();
newProduct.setDetails(details);
productRepository.save(newProduct);
return detailsRepository.save(details);
}
}
I am able to make the POST call to /details; for adding details successfully. But when i make GET call to /getAllProducts, I am getting this error
Cannot call sendError() after the response has been committed
This is an issue with bidirectional relationships, as they hold references to each other, at deserialization, Jackson runs in an infinite loop. My first suggestion would be adding #JsonIgnore to one end of the relation.
#OneToOne(mappedBy = "details")
#JsonIgnore
private Product product;
Afterward, if that solved your issue, you can look over #JsonManagedReference/#JsonBackReference and #JsonIdentityInfo.
You can also look over this link for more insight
You can use this :
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#JsonBackReference(value = "details_id")
#OneToOne(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL
)
#JoinColumn(name = "details_id")
private Details details;
//Getters and setters left out for brevity
}
#Entity
public class Details {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String description;
private float price;
private float discount;
#JsonManagedReference(value = "details")
#OneToOne(mappedBy = "details",,cascade=CascadeType.ALL)
private Product product;
}

JPA - Join another entity by EmbeddedId with 2 columns

Basically. I have a ProductEntity, like this:
#Entity
#Table(name = "product", schema = "shop")
public class ShopProductEntity {
#EmbeddedId
private ProductEntityId id;
#Column(name = "product_name")
private String name;
#Column(name = "product_category_id")
private int categoryId;
#Column(name = "product_language_id")
private int languageId;
// TODO: Get category by categoryId and languageId.
private CategoryEntity category;
I have another CategoryIdentity:
#Entity
#Table(name = "category", schema = "shop")
public class CategoryEntity {
#EmbeddedId
private CategoryEntityId id;
#Column(name = "category_name")
private String name;
#Column(name = "category_url")
private String url;
It has an EmbeddedId like this:
#Embeddable
public class CategoryEntityId implements Serializable {
#Column(name = "category_id", nullable = false)
private int categoryId;
#Column(name = "language_id", nullable = false)
private int languageId;
public int getCategoryId() {
return categoryId;
}
public int getLanguageId() {
return languageId;
}
Now, every product has a category. Categories are unique by their id and language. The shop connects to a category by both the categoryId and languageId columns. How do I add the CategoryEntity to my ProductEntity so I can use the category's url value for my product?
I tried adding this to ShopProductEntity:
#ManyToOne
#JoinColumns({
#JoinColumn(name="categoryId", referencedColumnName="categoryId"),
#JoinColumn(name="languageId", referencedColumnName="languageId"),
})
private CategoryEntity category;

Select on Northwind database using JPA and JDBC Template

I would like to perform select statement on Northwind database like this bellow.
select distinct b.*, a.CategoryName
from Categories a
inner join Products b on a.CategoryID = b.CategoryID
where b.Discontinued = 'N'
order by b.ProductName;
I have two problems regarding this operation :
I have created POJO for tables categories and products like bellow
Table Products
#Entity
public class Products {
#Id
private Long productid;
private String productname;
private Long supplierid;
#ManyToOne
#JoinColumn(name = "categories", referencedColumnName = "categoryid")
private Categories categoryid;
private String quantityperunit;
private Double unitprice;
private Long unitsinstock;
private Long unitsonorder;
private Long reorderlevel;
private String discontinued;
Table Categories
#Entity
public class Categories {
#Id
private Long categoryid;
private String categoryname;
private String description;
private String picture;
Now I have no idea how to write rowmapper for this tables (please find below ????)
private static final RowMapper<Products> productsRowMapper = (rs, rowNum) ->{
Products products = new Products();
products.setProductid(rs.getLong("ProductID"));
products.setProductname(rs.getString("ProductName"));
products.setSupplierid(rs.getLong("SupplierID"));
products.setCategoryid(rs.?????
products.setQuantityperunit(rs.getString("QuantityPerUnit"));
products.setUnitprice(rs.getDouble("UnitPrice"));
products.setUnitsinstock(rs.getLong("UnitsInStock"));
products.setUnitsonorder(rs.getLong("UnitsOnOrder"));
products.setReorderlevel(rs.getLong("ReorderLevel"));
products.setDiscontinued(rs.getString("Discontinued"));
return products;
};
the second problem is that I don't know if the annotations on the column categoryid in the table products are correct?
After correction
#Repository
public class JdbcProductsDao implements ProductsDao{
private final JdbcTemplate jdbcTemplate;
#Autowired
public JdbcProductsDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
private static final RowMapper<Products> productsRowMapper = (rs, rowNum) ->{
Products products = new Products();
products.setProductid(rs.getLong("ProductID"));
products.setProductname(rs.getString("ProductName"));
products.setSupplierid(rs.getLong("SupplierID"));
products.setCategoryid(new Categories(rs.getString("CategoryName")));
products.setQuantityperunit(rs.getString("QuantityPerUnit"));
products.setUnitprice(rs.getDouble("UnitPrice"));
products.setUnitsinstock(rs.getLong("UnitsInStock"));
products.setUnitsonorder(rs.getLong("UnitsOnOrder"));
products.setReorderlevel(rs.getLong("ReorderLevel"));
products.setDiscontinued(rs.getString("Discontinued"));
return products;
};
public Products findByProductName(String productname) {
String sql = "SELECT * FROM products WHERE ProductName = ?";
return jdbcTemplate.queryForObject(sql, productsRowMapper, productname);
}
public List<Products> sortByProductName(){
String sql = "SELECT * FROM products order by ProductName asc";
return jdbcTemplate.query(sql, productsRowMapper);
}
Table Categories
#Entity
public class Categories {
#Id
private Long categoryid;
#Column(name = "CategoryName")
private String categoryname;
private String description;
private String picture;
#OneToMany(mappedBy="categoryid")
private List<Products> products;
Table Products
#Entity
public class Products {
#Id
private Long productid;
#Column(name = "ProductName")
private String productname;
private Long supplierid;
#ManyToOne
private Categories categoryid;
private String quantityperunit;
private Double unitprice;
private Long unitsinstock;
private Long unitsonorder;
private Long reorderlevel;
private String discontinued;
Now I have no idea how to write rowmapper for this tables (please find
below ????)
You have to set an Object like this :
products.setCategoryid(new Categories(rs.getString("a.categoryname"));
I assume you have an constructor Categories(String categoryname)
Note : If you want to get more information, you have to change your query and your constructor as well.
the second problem is that I don't know if the annotations on the
column categoryid in the table products are correct?
I think there are no need to use #JoinColumn(name = "categories", referencedColumnName = "categoryid") just use :
#ManyToOne
private Categories categoryid;
In your Categories entities add this :
#OneToMany(mappedBy="categoryid")
List<Products> products;

Resources