N-N relationship with additional fields (problems to persist data)

Asked

Viewed 785 times

4

I have an N-N relationship that works as follows:

Destination.java (N)-(N) Customerservice.java

Within this relationship there is an entity that keeps the relationship Ids, which has some more values. The name of such is Serviceitem.java

Below the Entity’s:

Destination.java

@Entity
@Table(name="destination")
public class Destination implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id_destination")
    private Long idDestination;

    @Column(name="tenant_id", insertable=false, updatable=false)
    private Long tenantId;

    @Column(name="appear_website")
    private Boolean dtAppearWebsite;

    @Lob
    @Column(name="description")
    @NotEmpty(message="O campo \"Descrição do Destino\" não pode estar em branco.")
    private String dtDescription;

    @OneToMany(mappedBy="destination")
    private Set<ServiceItem> serviceItem;

    //Mais campos adicionais e Getters and Setters

    public Destination() {
    }
}

Customerservice.java

@Entity
@Table(name = "customer_service")
public class CustomerService implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id_customer_service")
    private Long id;

    @Column(name = "tenant_id", insertable = false, updatable = false)
    private Long tenantId;

    @Column(name = "date_service")
    @DateTimeFormat(pattern = "dd/MM/yyyy")
    @Temporal(TemporalType.DATE)
    private Date date;

    @Column(name = "average_budget")
    private BigDecimal averageBudget;

    @Column(name = "service_situation")
    private boolean situation;

    @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "customerService")
    private List<ServiceItem> serviceItem;

    @OneToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "fk_history")
    private History history;

    @Column(name = "service_observatons")
    private String serviceObservations;

    public CustomerService() {

    }

    //Mais campos adicionais e Getters and Setters

}

Serviceitem.java

@Entity
@Table(name = "service_item")
@IdClass(ServiceItemId.class)
public class ServiceItem {

    @Id
    @ManyToOne
    @JoinColumn(name="destination_id")
    private Destination destination;

    @Id
    @ManyToOne
    @JoinColumn(name="customerService_id")
    private CustomerService customerService;

    @Column(name="tenant_id", insertable=false, updatable=false)
    private Long tenantId;   

    @Column(name="value_negotiated")
    private double valueNegotiated;

    @Enumerated(EnumType.STRING)
    @Column(name="sale_type")
    private SaleType saleType;

    @Column(name="departure_date")
    @DateTimeFormat(pattern="dd/MM/yyyy")
    @Temporal(TemporalType.DATE)
    private Date departureDate;

    @Column(name="arrival_date")
    @DateTimeFormat(pattern="dd/MM/yyyy")
    @Temporal(TemporalType.DATE)
    private Date arrivalDate;

    @Column(name="requested_destination")
    private Boolean requestedDestination;

    @Column(name="negociation_observations")
    private String negociationObservations;

    public ServiceItem() {

    }

    //Getters and Setters
}

To generate id’s there is the class Serviceitemid.java

public class ServiceItemId implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long destination;
    private Long customerService;

    public Long getCustomerService() {
        return customerService;
    }
    public void setCustomerService(Long customerService) {
        this.customerService = customerService;
    }
    public Long getDestination() {
        return destination;
    }
    public void setDestination(Long destination) {
        this.destination = destination;
    }

    @Override
    public int hashCode() {
        return (int) (destination + customerService);
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof ServiceItemId){
            ServiceItemId serviceItemId = (ServiceItemId) obj;
            return serviceItemId.destination == destination && serviceItemId.customerService == customerService;
        }

        return false;
    }

}

Well, the question is when will I persist the data...

Dez 17, 2014 4:52:27 PM org.apache.catalina.core.StandardWrapperValve invoke
GRAVE: Servlet.service() for servlet [appServlet] in context with path [/viatge] threw exception [Request processing failed; nested exception is org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20131113-a7346c6): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'customerService_id' cannot be null
Error Code: 1048
Call: INSERT INTO service_item (arrival_date, departure_date, negociation_observations, requested_destination, sale_type, value_negotiated, customerService_id, destination_id, tenant_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
    bind => [2016-08-01, 2015-10-01, teste, true, SUBMITTED_BUDGET, 3658.98, null, 1, 2]
Query: InsertObjectQuery(br.com.joocebox.model.ServiceItem@6e92b1b1)] with root cause
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'customerService_id' cannot be null
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
    at com.mysql.jdbc.Util.getInstance(Util.java:386)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1041)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4187)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4119)

I even understand that it is missing inform the id customerService_id in the Serviceitem.java entity, but this id will be generated automatically, as it is incorrect for me to enter the id in hand.

How could I proceed in such a case?

EDITION

Come on!

My Entity Customerservice (which refers to a service) has many destinations (in this case Destination), as many destinations may belong to many Customerservice.java (support).

Note that there is no relationship @Manytomany directly between the entities, but there is a class called Serviceitem that makes the union between the two entities.

When I will persist a Serviceitem, my id related to Destination is loaded, (because it already exists), already the Customerservice has not been persisted, so it will not have an ID which is the cause of the problem.

  • 1

    Why is it incorrect to inform the ID "at hand"? Where was this information taken from? You can very well use a method with @Prepersist and assign a UUID to your key for example. This is what I usually use, even more in distributed systems, is the fastest solution I see to avoid bottleneck with sequential id generation in concurrent environment. I’m not sure I understand your application (specify better if my answer is vacant). From a business point of view, it is necessary to have a composite primary key ? why not just use a specific field for the ID ?

  • 1

    Well, @Josuéeduardo, in my understanding it would not be a "good practice" to inform the id’s, but it seems I’m wrong. Well I’m performing the mapping between the entities so Many-to-Many with additional fields. The form of which it was made was the same found in this link: http://uaihebert.com/jpa-mini-bookletFirst Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step Step because my idea was that customerService_id would be generated automatically. Grateful for the clarification

  • 2

    Just one more detail: unfortunately Serviceitemid.hashcode() has been implemented incorrectly, 1+2=3 and 2+1=3, I advise you to take a look here: http://stackoverflow.com/questions/10034328/hashcode-for-objects-with-only-integers... Regarding the question in question: Are you sure that the two entities that make up the primary key are not null ? If you have any more details about the status of Destination and Customerservice I think it would help us a little better. What I think is that Serviceitem was not instantiated.

  • 1

    I won’t be long because I’m on a cell phone. But I’ll be direct. 1- When 1 saw N-N in the title, I imagined the association MANY to MANY (I went to look up the @Manytomany annotation and couldn’t find it); 2- I find this mapping mode very confusing, if you have composite keys, why don’t you use Embeddable ? ; 3- Avoid using Prepersist to the fullest, if an id is required to be generated, make this explicit in your software’s business layer; 4- as mentioned above, correctly implement the hashcode.

  • Thank you for observing the poor implementation of hashcode(), I will fix it. So they are not null. The problem occurs with the Customerservice id, as it has not yet persisted in the database, so the integrity problem occurs. The serviceItem cannot perform the Insert precisely for the reason that the Customerservice id is missing. The question is whether there is a way to generate such an id automatically. or I should record a Customerservice first and then record a Serviceitem?

  • @wryel, such an example has been picked up in books and websites, as I am using the N-N approach with additional fields (http://uaihebert.com/jpa-mini-booklet-first passos-passos-e-concettes/22/). So far I only know this approach.

  • 1

    Then do so please edit your question and there at the end put the scenario you are trying to accomplish, e.g.: 1 person has N Children, 1 Son has N carts. Make it clearer to us that we’re trying to help. (Taking advantage, the problem with this Idclass note is that it marries more legacy systems than new systems, in which Embeddable makes much more sense [in fact you make a class that takes on the identity of an entity])

  • @wryel, I did the editing well mitigating the situation, as I changed the question (I hope you are legal).

  • @Josuéeduardo, I was reading about the implementation of a UUID for id (performing a @Prepersist) and I thought it would solve my problem perfectly. To be honest I did not know this form of generation of id’s. Could you illustrate me an example and comment (if not too laborious) what are the advantages and disadvantages of such an approach? Grateful!

Show 4 more comments

1 answer

1


Responding to the discussion in the comments: Both the use of UUID(GUID) and ID have their pros and cons.

The more technical details I leave to the original post, which this response is based on:

Soen - Advantages-and-disadvantages-of-guid-uuid-database-Keys

Soen - how-do-you-like-your-Primary-Keys

Soen - generate-uuid-in-java

Follow a simple example, note that @Prepersist will be executed by your ORM framework. More details on Entity Systems HERE.

@Entity
@Table(name = "service_item")
public class ServiceItem {

    @Id
    //Long ? Integer ?
    private String uuid;

    @ManyToOne
    @JoinColumn(name="destination_id")
    private Destination destination;

    @ManyToOne
    @JoinColumn(name="customerService_id")
    private CustomerService customerService;


    @PrePersist
    public void init() {
        //Aqui voce gera seu GUID
        //Note que existem varios outros meios
        //Estou utilizando esse pois é nativo e pratico
        this.uuid = UUID.randomUUID().toString();
    }

    //...
}
  • Unfortunately, it didn’t work out. I made the implementation in my Entity Customerservice.java following this tutorial http://www.thebinaryidiot.com/archives/2011/06/25/jpa-anduuid-primary-keys/, but the error still persists. I confess I don’t know where else to run. I believe that now the way that would most fit my problem would be to persist my Customerservice perimeiro object, so that in sequence (already with the id of Customerservice), I can record the service.

  • 1

    @Joãomanolo, if an entity C has reference to two other entities A and B, at the moment of persisting C, A and B must be in the context of persistence (already persisted). That’s a fundamental DB rule. As for the example I gave you, note that there is no magic, Voce can instead of using the method annotated with @Prepersist, directly assign a uuid: serviceItem.setUuid("uuid-123") to then insert into the base, no problem. As to your problem you should insert both Destination, how much CustomerService before persisting ServiceItem

Browser other questions tagged

You are not signed in. Login or sign up in order to post.