Ну, как говорится в названии, у меня есть много разных отношений в моем проекте, когда у клиента может быть много купонов и наоборот. Чтобы сделать эту работу, я сделал еще одну таблицу в MySQL, которая включает идентификатор купона и идентификатор клиента (каждая строка), но почему-то каждый раз, когда я добавляю купон клиенту, он удваивает свои строки в таблице coupon_customer. например:
coupon-> id 1
customer-> id 4
теперь я добавляю еще один купон (id 2) одному клиенту и что результат:
мой код:
Покупатель:
@Entity
@Table(name = "customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "name")
private String name;
@Column(name = "password")
private String password;
@ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH,
CascadeType.REFRESH })
@JoinTable(name = "coupon_customer", joinColumns = @JoinColumn(name = "customer_id"), inverseJoinColumns = @JoinColumn(name = "coupon_id"))
private List<Coupon> coupons;
public Customer() {
}
public Customer(String name, String password) {
this.name = name;
this.password = password;
this.coupons = new ArrayList<Coupon>();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@JsonIgnore
public List<Coupon> getCoupons() {
return coupons;
}
public void setCoupons(ArrayList<Coupon> coupons) {
this.coupons = coupons;
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + ", password=" + password + "]";
}
}
Купон:
@Entity
@Table(name = "coupon")
public class Coupon {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "title")
private String title;
@Column(name = "start_date")
private Date startDate;
@Column(name = "end_date")
private Date endDate;
@Column(name = "amount")
private int amount;
@Enumerated(EnumType.STRING)
@Column(name = "type")
private CouponType type;
@Column(name = "message")
private String message;
@Column(name = "price")
private double price;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "company_id")
private Company company;
@ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH,
CascadeType.REFRESH })
@JoinTable(name = "coupon_customer", joinColumns = @JoinColumn(name = "coupon_id"), inverseJoinColumns = @JoinColumn(name = "customer_id"))
private List<Customer> customers;
public Coupon() {
}
public Coupon(String title, Date startDate, Date endDate, int amount, CouponType type, String message,
double price) {
this.title = title;
this.startDate = startDate;
this.endDate = endDate;
this.amount = amount;
this.type = type;
this.message = message;
this.price = price;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public CouponType getType() {
return type;
}
public void setType(CouponType type) {
this.type = type;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@JsonIgnore
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
@JsonIgnore
public List<Customer> getCustomers() {
return customers;
}
public void setCustomers(List<Customer> customers) {
this.customers = customers;
}
@Override
public String toString() {
return "Coupon [id=" + id + ", title=" + title + ", startDate=" + startDate + ", endDate=" + endDate
+ ", amount=" + amount + ", type=" + type + ", message=" + message + ", price=" + price + "]";
}
CustomerController:
@RequestMapping(value = "/purchaseCoupon")
public ResponseEntity<CouponSystemResponse> purchaseCoupon(@RequestParam(value = "id") int id) {
try {
Coupon coupon = couponService.getCoupon(id);
getEntity().getCoupons().add(coupon); --> getEntity() gets the customer
coupon.setAmount(coupon.getAmount() - 1);
customerService.updateCustomer(getEntity()); --> updates customer after purchase coupon
couponService.updateCoupon(coupon); --> update coupon after been purchased(amount -1)
.....
и если это поможет MySQL-скрипту:
DROP SCHEMA IF EXISTS 'couponsystem';
CREATE SCHEMA 'couponsystem';
use 'couponsystem';
DROP TABLE IF EXISTS 'company';
CREATE TABLE 'company' (
'id' int(11) NOT NULL AUTO_INCREMENT,
'name' varchar(20) NOT NULL UNIQUE,
'password' varchar(20) NOT NULL,
'email' varchar(20) DEFAULT NULL,
PRIMARY KEY ('id')
);
DROP TABLE IF EXISTS 'coupon';
CREATE TABLE 'coupon' (
'id' int(11) NOT NULL AUTO_INCREMENT,
'title' varchar(20) NOT NULL UNIQUE,
'start_date' datetime DEFAULT NULL,
'end_date' datetime DEFAULT NULL,
'amount' int DEFAULT NULL,
'type' varchar(15) DEFAULT NULL,
'message' varchar(50) DEFAULT NULL,
'price' float DEFAULT NULL,
'company_id' int(11),
PRIMARY KEY ('id'),
KEY 'FK_company_id' ('company_id'),
CONSTRAINT 'FK_company_id' FOREIGN KEY ('company_id') REFERENCES 'company' ('id') ON DELETE CASCADE ON UPDATE CASCADE
);
DROP TABLE IF EXISTS 'customer';
CREATE TABLE 'customer' (
'id' int(11) NOT NULL AUTO_INCREMENT,
'name' varchar(20) NOT NULL UNIQUE,
'password' varchar(20) NOT NULL,
PRIMARY KEY ('id')
);
CREATE TABLE 'coupon_customer'(
'coupon_id' int(11) NOT NULL,
'customer_id' int(11) NOT NULL,
/*
PRIMARY KEY ('coupon_id','customer_id'), --> that in comment only cause I got exception every time row doubles itself and tried looking for solutions
*/
CONSTRAINT 'FK_coupon_id' FOREIGN KEY ('coupon_id') REFERENCES 'coupon' ('id') ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT 'FK_customer_id' FOREIGN KEY ('customer_id') REFERENCES 'customer' ('id') ON DELETE CASCADE ON UPDATE CASCADE
);
Обслуживание клиентов:
@Service
public class CustomerService {
@Autowired
CustomerRepository customerRepo;
.....
public void updateCustomer(Customer customer) {
customerRepo.save(customer);
}
.....
CouponService:
@Service
public class CouponService {
@Autowired
CouponRepository couponRepo;
......
public void updateCoupon(Coupon coupon) {
couponRepo.save(coupon);
}
......
Странные вещи. Как и все последние строки, их добавить, а затем добавить еще несколько строк. Я думал, что-то с каскадом, но не мог сделать эту работу... ценю любую помощь.
Первичный ключ Coupon_Customer
должен состоять из двух полей (customer_id
и coupon_id
).
Глядя на ваш код, у вас нет первичного ключа в этой таблице. Это главная проблема.
Чтобы создать @Embeddable
первичный ключ в Spring Data JPA, вам понадобится аннотированный класс @Embeddable
, который будет представлять ваш coupon_customer_id
.
Что-то вроде следующего:
CouponCustomerId.java
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class CouponCustomerId implements Serializable {
@Column(name = "coupon_id")
private Long couponId;
@Column(name = "customer_id")
private Long customerId;
public CouponCustomerId(Long couponId, Long customerId) {
this.couponId = couponId;
this.customerId = customerId;
}
// getters and setters..
}
Теперь вам нужно создать объект CouponCustomer с @EmbeddedId
, который будет представлять ваш @EmbeddedId
первичный ключ.
CouponCustomer.java
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.OneToMany;
@Entity
public class CouponCustomer {
@EmbeddedId
private CouponCustomerId id;
@ManyToOne(fetch = FetchType.LAZY) // add your own ManyToOne configurations
@MapsId("couponId")
private Coupon coupon;
@ManyToOne(fetch = FetchType.LAZY) // add your own ManyToOne configurations
@MapsId("customerId")
private Customer customer;
// getters and setters..
}
Теперь в вашем Customer
сущности, вы должны изменить свой List<Coupon>
в List<CouponCustomer>
и изменить отношение к @OneToMany
.
Customer.java
....
@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
})
private List<CouponCustomer> coupons;
....
То же самое для Coupon
объекта.
Coupon.java
....
@OneToMany(mappedBy = "coupon" fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
})
private List<CouponCustomer> customers;
....
Теперь, каждый раз, когда вы добавляете купон к клиенту, вам просто нужно связать его с идентификаторами.
Что-то вроде следующего:
@RequestMapping(value = "/purchaseCoupon")
public ResponseEntity < CouponSystemResponse > purchaseCoupon(@RequestParam(value = "id") int id) {
try {
Coupon coupon = couponService.getCoupon(id);
coupon.setAmount(coupon.getAmount() - 1);
couponService.updateCoupon(coupon); -->update coupon after been purchased(amount - 1)
CouponCustomer cc = new CouponCustomer();
// assuming that getEntity() gets your Customer
cc.setCoupon(coupon);
cc.setCustomer(getEntity();
cc.setId(new CouponCustomerId(coupon.getId(), getEntity().getId()));
couponCustomerService.save(cc);
.....
Имейте в виду, что для обновления Coupon
и создания записи в Coupon_customer
вам не нужно вызывать customerService.updateCustomer
.
В
cc.setId(new CouponCustomerId(coupon.getId(), getEntity().getId()));
couponCustomerService.save(cc);
Вы создаете запись в таблицу coupon_customer с составленным первичным ключом (coupon_id
, customer_id
).
Надеюсь это поможет.
В первую очередь я добавлю еще одно ограничение на таблицу coupon_customer, уникальную комбинацию, снабженную INSERT IGNORE conmand, которая будет пропускать ошибки вставки, она обеспечит базовую db-защиту таких ошибок
ALTER TABLE coupon_customer ADD UNIQUE KEY coupon_customer (coupon_id, customer_id);
и INSERT должен быть:
INSERT IGNORE INTO...
Кроме того, функция, генерирующая запрос, должна получать ровно один параметр для каждого ключа и генерировать простейший запрос. Если вставка js, построенная с помощью select или функция, работает с функцией с массивами, тогда они могут генерировать ошибки, как вы описали
public function add coupon($customer_id, $coupon_id) {
...
$sql = "INSERT IGNORE INTO coupon_customer VALUES (". $customer_id . ",". $coupon_id . ");" ;
...
}
coupon_id
КЛЮЧ ( coupon_id
, customer_id
),