Отношение многие ко многим - получить двойные строки в MySQL

0

Ну, как говорится в названии, у меня есть много разных отношений в моем проекте, когда у клиента может быть много купонов и наоборот. Чтобы сделать эту работу, я сделал еще одну таблицу в 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);
    }
    ......

Странные вещи. Как и все последние строки, их добавить, а затем добавить еще несколько строк. Я думал, что-то с каскадом, но не мог сделать эту работу... ценю любую помощь.

  • 0
    где находится функция, выполняющая запросы INSERT? Я их не вижу ... это вставка select, как функция insert получает купон и клиента?
  • 0
    @GuyL Я использую CrudRepository с Hibernate ...
Показать ещё 5 комментариев
Теги:
spring-boot
spring
hibernate

2 ответа

0

Первичный ключ 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).

Надеюсь это поможет.

  • 0
    Возникла исключительная ситуация во время инициализации контекста - отмена попытки обновления: org.springframework.beans.factory.BeanCreationException: ошибка создания бина с именем entityManagerFactory, определенным в ресурсе пути к классу [org / springframework / boot / autoconfigure / orm / jpa / HibernateJpaConfiguration.class] : Вызов метода init не выполнен; вложенным исключением является org.hibernate.AnnotationException: внешний ключ, ссылающийся на com.orel.couponsystem.entity.CouponCustomer из com.orel.couponsystem.entity.Coupon, имеет неправильный номер столбца. должно быть 2
  • 0
    Спасибо за помощь. Теперь я получаю это исключение при попытке запустить приложение :(: выше меня) @Matheus Cirillo
Показать ещё 5 комментариев
0

В первую очередь я добавлю еще одно ограничение на таблицу 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 . ");" ;
... 
} 
  • 0
    У меня уже есть это, братан ... проверьте сообщение coupon_id КЛЮЧ ( coupon_id , customer_id ),
  • 0
    это частичное решение, так как оно защищает БД (как в 1-й части моего ответа), но оно не исправит код, который все еще генерирует неправильный запрос

Ещё вопросы

Сообщество Overcoder
Наверх
Меню