Изключение при записване в JPA дефиниция с Spring Boot + Spring данни JPA

Имам проблем с дефиниция на JPA, използвайки Spring Boot + Spring Data JPA, дефинирах следните обекти:

CommerceCnae.java

@Entity
public class CommerceCnae implements Serializable {

    private static final long serialVersionUID = -5071526484444404982L;

    @Id
    private String name;

    @OneToMany( mappedBy = "commerce", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<AddressCnae> addresses;

    @OneToMany( mappedBy = "commerceCategory", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<CategoryCnae> categories;

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name
     *            the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the addresses
     */
    public List<AddressCnae> getAddresses() {
        return addresses;
    }

    /**
     * @param addresses
     *            the addresses to set
     */
    public void setAddresses(List<AddressCnae> addresses) {
        this.addresses = addresses;
    }

    /**
     * @return the categories
     */
    public List<CategoryCnae> getCategories() {
        return categories;
    }

    /**
     * @param categories
     *            the categories to set
     */
    public void setCategories(List<CategoryCnae> categories) {
        this.categories = categories;
    }

}

КатегорияCnae.java

@Entity
public class CategoryCnae implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 3889422753757007724L;
    @Id
    private String id;
    private String description;

    @ManyToOne
    @JoinColumn(name = "name", nullable = false)
    private CommerceCnae commerceCategory;

    /**
     * @return the id
     */
    public String getId() {
        return id;
    }

    /**
     * @param id
     *            the id to set
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * @return the description
     */
    public String getDescription() {
        return description;
    }

    /**
     * @param description
     *            the description to set
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * @return the commerceCategory
     */
    public CommerceCnae getCommerceCategory() {
        return commerceCategory;
    }

    /**
     * @param commerceCategory
     *            the commerceCategory to set
     */
    public void setCommerceCategory(CommerceCnae commerceCategory) {
        this.commerceCategory = commerceCategory;
    }

}

АдресCnae.java

@Entity
public class AddressCnae implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 7387779987115612860L;

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

    private String city;
    private String address;

    @ManyToOne
    @JoinColumn(name = "name", nullable = false)
    private CommerceCnae commerce;

    /**
     * @return the city
     */
    public String getCity() {
        return city;
    }

    /**
     * @param city
     *            the city to set
     */
    public void setCity(String city) {
        this.city = city;
    }

    /**
     * @return the address
     */
    public String getAddress() {
        return address;
    }

    /**
     * @param address
     *            the address to set
     */
    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public boolean equals(Object obj) {
        if ((obj instanceof AddressCnae)) {
            AddressCnae addressCnaeObj = (AddressCnae) obj;
            return (addressCnaeObj.getAddress().equals(this.getAddress()) && addressCnaeObj.getCity().equals(this.city));
        } else {
            return false;

        }
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + this.address.hashCode() + this.city.hashCode();
        return result;
    }

    /**
     * @return the commerce
     */
    public CommerceCnae getCommerce() {
        return commerce;
    }

    /**
     * @param commerce
     *            the commerce to set
     */
    public void setCommerce(CommerceCnae commerce) {
        this.commerce = commerce;
    }

    /**
     * @return the addressCnaeId
     */
    public long getAddressCnaeId() {
        return addressCnaeId;
    }

    /**
     * @param addressCnaeId
     *            the addressCnaeId to set
     */
    public void setAddressCnaeId(long addressCnaeId) {
        this.addressCnaeId = addressCnaeId;
    }

}

И ето моето хранилище:

@Repository
public interface CommerceRepository extends JpaRepository<CommerceCnae, String> {

}

Но при запазване имам следващото изключение:

2015-04-08 11:22:31.320 DEBUG 18145 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper   : could not execute statement [n/a]

java.sql.SQLException: Field 'commerce' doesn't have a default value
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:996)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3887)

Това е двупосочна връзка и моята конфигурация е следната:

#DATASOURCE CONFIGURATION

spring.datasource.url=jdbc:mysql://XXXXX/somthing
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.username=XXXXX
spring.datasource.password=XXXXX
spring.datasource.continueOnError=false
spring.datasource.max-active=5

#JPA CONFIGURATION
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.generate-ddl=true
spring.jpa.hibernate.hbm2ddl.auto=create-drop

Какво не е наред?

Благодаря за вашата помощ!

Редактиране:

Данни за запазване:

{  
   "@type":"es.domain.jpa.commerce.CommerceCnae",
   "name":"LAICOS S A",
   "addresses":{  
      "@type":"java.util.ArrayList",
      "@items":[  
         {  
            "@type":"es.domain.jpa.commerce.AddressCnae",
            "addressCnaeId":0,
            "city":"31200 ESTELLA - ",
            "address":"CL WITHOUTNUMBER 00033",
            "commerce":null
         }
      ]
   },
   "categories":{  
      "@type":"java.util.ArrayList",
      "@items":[  
         {  
            "@type":"es.domain.jpa.commerce.CategoryCnae",
            "id":"6441",
            "description":"OTHER DESCRIPTION.",
            "commerceCategory":null
         }
      ]
   }
}

Сложих nullable = true и не работи. Същата грешка.

РЕДАКТИРАНЕ С РЕШАВАНЕ:

Проблемът е в инициализацията, нещо странно се случва, когато поставите @ManyToOne или @OneToMany релации и опция spring.jpa.hibernate.hbm2ddl.auto=create-drop. Изтрих базата данни и инициализирах с spring.jpa.hibernate.hbm2ddl.auto=update. Решено е!! Благодаря за вашата помощ!!


person Ganchix    schedule 08.04.2015    source източник
comment
Не съм сигурен, но това е bcoz указали сте not null и/или default value ограничения в SQL скрипта, а не в POJO. Опитайте да генерирате повторно таблици чрез Hibernate автоматично и след това проверете за същото. След това сравнете SQL скрипта и ще разберете истинската причина.   -  person OO7    schedule 08.04.2015
comment
Опитайте да инициализирате списъците например: private List<AddressCnae> addresses = new ArrayList<>();   -  person Robert Niestroj    schedule 08.04.2015
comment
Имате nullable = false на commerce и вероятно не задавате никаква стойност...   -  person Evgeni Dimitrov    schedule 08.04.2015
comment
Списъкът за инициализиране не работи, добавено е представянето на обект за запазване в JSON.   -  person Ganchix    schedule 08.04.2015
comment
Сложих nullable = true и не работи. Същата грешка. Благодаря!   -  person Ganchix    schedule 08.04.2015
comment
Опитвали ли сте да създавате таблици чрез Hibernate? Какъв резултат получихте от него?   -  person OO7    schedule 08.04.2015
comment
Благодаря @OO7, изтрих всички таблици и това ми предоставя друго изключение org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : es.domain.jpa.commerce.AddressCnae.commerce -> es.domain.jpa.commerce.CommerceCnae; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException:object references an unsaved transient instance- save the transient instance before flushing : es.domain.jpa.commerce.AddressCnae.commerc -> es.domain.jpa.commerce.CommerceCnae Работя в него   -  person Ganchix    schedule 08.04.2015
comment
Тази грешка означава, че се опитвате да запазите екземпляр на обект, който има един или повече незапазени зависими обекти.   -  person OO7    schedule 08.04.2015
comment
Но дефинирах двупосочна връзка, предполагам, че когато запазя CommerceCnae отношенията с AddressCnae и CategoryCnae се записват автоматично, правилно ли е?   -  person Ganchix    schedule 08.04.2015
comment
Но сега ми върнете друго изключение: org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): es.domain.jpa.commerce.CommerceCnae, това ме подлудява :(   -  person Ganchix    schedule 08.04.2015
comment
CommerceCnae.name е от тип String. Така че трябва да зададете стойност за него и тя трябва да бъде уникална. Задали ли сте стойност за това поле?   -  person OO7    schedule 08.04.2015
comment
Проблемът е в инициализацията, нещо странно се случва, когато поставите @ManyToOne или @OneToMany релации и опция spring.jpa.hibernate.hbm2ddl.auto=create-drop. Изтрих базата данни и инициализирах с spring.jpa.hibernate.hbm2ddl.auto=update. Решено е!! Благодаря за вашата помощ!!   -  person Ganchix    schedule 08.04.2015
comment
Проверете дали можете да прочетете правилните данни обратно от DB, като гледам кода ви, предполагам, че няма да получите никакви адреси за вашата търговия. Вижте отговора по-долу. Освен ако, разбира се, вече не сте го поправили междувременно.   -  person Zielu    schedule 08.04.2015


Отговори (1)


Разглеждайки вашите примерни данни и коментарите, те са малко проблеми.

Вашите JSON данни представляват дървото CommerceCae, което:

  • има един адрес с идентификатор 0 и няма обратна препратка към действителния CommerceCae (commerce == null)
  • има една категория с идентификатор 6441 и няма обратна връзка към действителния CommerceCae

Сега в конфигурацията на JPA вие задавате отношенията ManyToOne да се обработват (mappedBy) на обектите на адреса или категорията. Така че търговската стойност трябва да бъде зададена преди постоянната. Входните данни ги нямаха, така че получихте вашето SQL изключение, тъй като колоната за присъединяване имаше нулева стойност, която не беше позволена (между другото трябва да въведете nullable=false отново и да генерирате отново таблиците, беше добре в началото, вие просто обработихте обектите грешно).

Така че, след като прочетете своите JSON данни, трябва да коригирате връзките:

CommerceCnae c = ....
for (AddressCnae a : c.getAddresses()) a.setCommerce(c);
for (CategoryCnae cat : c.getCategories()) cat.setCommerceCategory(c);
....
EM.persist(c);

Това ще реши вашето java.sql.SQLException: Field 'commerce' doesn't have a default value

Сега във вашия CommerceCnae няма стратегия за генериране на ID, така че преди запазването трябва да зададете ID ръчно. Тъй като вашите JSON данни изглежда нямат ID поле, предполагам, че това е обикновен пропуск и искате да имате ID, генериран от JPA, така че добавете @GeneratedValue(strategy = GenerationType.AUTO) към полето Commerce Id.

Вашият клас Category няма стратегия за генериране на ID, което е правилно, тъй като вашите JSON данни вече съдържат стойностите на ID. Но вашите JSON данни имат идентификатор за адрес, но вие сте избрали идентификаторът да се генерира автоматично от JPA в неговия POJO обект. Не знам дали беше нарочно или просто съвпадение.

person Zielu    schedule 08.04.2015