Hibernate #Version causing database foreign key constraint failure - java

I have two hibernate/JPA entities
#Entity
#Table(name = "conference_room", uniqueConstraints = #UniqueConstraint(columnNames = "code"))
class ConferenceRoom {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "code", unique = true, length = 20)
private String code;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "conferenceRoom")
#Cascade({CascadeType.ALL})
private Set<Person> people = new HashSet<Person>();
// Appropriate getters and setters
}
#Entity
#Table(name = "person")
class Person {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "conference_room_code", referencedColumnName = "code")
private ConferenceRoom conferenceRoom ;
// Appropriate getters and setters
}
More over in my database schema I have foreign key contraint on person.conference_room_code that references conference_room.code column.
With in a spring #Transactional method if I do following
public ConferenceRoom getNewConferenceRoom(Person p) {
ConferenceRoom r = new ConferenceRoom();
r.setCode("MyUniqueGeneratedCode");
r.getPeople().add(p);
// sessionFactory is spring injected member
sessionFactory.getCurrentSession().merge(r);
}
Everything is saved correctly, the row for person is updated properly and new conference_room row is added.
But then I tried to add support for optimistic locking db updates to Person class on Date field, so new Person class
#Entity
#Table(name = "person")
class Person {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "conference_room_code", referencedColumnName = "code")
private ConferenceRoom conferenceRoom ;
#Version
#Column(name = "updated", length = 19)
private Date updated;
// Appropriate getters and setters
}
This new 'updated' column in MYSQL timestamp column with default value of current_timestamp on insert and updates.
Now if the above method is executed I get
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Cannot add or update a child row: a foreign key constraint fails (`schema`.`person`, CONSTRAINT `fk_room_code` FOREIGN KEY (`conference_room_code`) REFERENCES `conference_room` (`code`) ON DELETE NO ACTION ON UPDATE NO ACTION)
....
I tried adding a #Version field to ConferenceRoom, but that didn't work.
I can't understand why adding #Version messes things up.
If I remove foreign key constraint or the newly added #Version field, code again starts working, without any exceptions.
I don't want to drop the foreign key constraint. Is there any other way around this problem.

First:
More over in my database schema I have foreign key contraint on person.conference_room_code that references conference_room.code column.
Your FK should reference the PK of the referenced entity. In the instant case, you should have person.conference_room_id which references conferenceroom.id. If you want your code to be the identifying field for the ConferenceRoom entity, then don't use a surrogate key. If the code column isn't a PK candidate, then it is also not an FK candidate.
Second:
Merge:
Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The given instance does not become associated with the session. This operation cascades to associated instances if the association is mapped with cascade="merge"
Persist:
Make a transient instance persistent. This operation cascades to associated instances if the association is mapped with cascade="persist"
I think you have confused merge with persist. From what I can tell by the provided code, you are creating a new ConferenceRoom and not modifying an existing one. Therefore, merge is not going to do what you want it to do. Try changing your (provided) method to the following:
public ConferenceRoom getNewConferenceRoom(Person p) {
ConferenceRoom r = new ConferenceRoom();
r.setCode("MyUniqueGeneratedCode");
r.getPeople().add(p);
// sessionFactory is spring injected member
sessionFactory.getCurrentSession().persist(r);
}
These things should fix the issues you have raised.

Related

JPA not saving foreign key in one-to-many relation

I have 2 tables with one-to-many relation on the owner class (Person) and many-to-one on the child class (Email)
My problem is that in the child class' foreign key is (person_id) is always null when I want to save my Person object. I tried different things using other questions' answers, but no luck.
I would like to solve this in an annotation approach, if it is possible.
Person Class:
#Entity
#Table(name="PERSON")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_PERSON")
#SequenceGenerator(name="SEQ_PERSON", sequenceName="SEQ_PERSON", allocationSize=1)
#Column(name = "person_id")
private Long personId;
#OneToMany(mappedBy="person", cascade=CascadeType.ALL)
private List<Email> email;
// getters and setters
}
Email class:
#Entity
#Table(name="EMAIL")
public class Email{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_EMAIL")
#SequenceGenerator(name="SEQ_EMAIL", sequenceName="SEQ_EMAIL", allocationSize=1)
#Column(name = "email_id")
private Long emailId;
#ManyToOne
#JoinColumn(name="person_id", referencedColumnName="person_id", insertable = true)
private Person person;
// getters and setters
}
I get no exception / errors when I use this.
When I change the JoinColumn to #JoinColumn(name="person_id", referencedColumnName="person_id", nullable = false, updatable = false, insertable = true) then I get this error: org.hibernate.PropertyValueException: not-null property references a null or transient value: com.test.Email.person
I tried to change the Person's email setter like this, nothing changed:
public synchronized void setEmail(List<Email> email) {
this.email=email;
for(Email em: email) {
em.setPerson(this);
}
}
source
I have a Person object, with 2 emails (as a test object to save, every column is filled, except the FK in Email table), do I have to set the FK everytime manually? (it doesn't look good, if I have multiple one-to-many variables)
Edit: I tried this Which is working, but my problem with that if I have a very deep data structure with a lot of One-To-Many relations, I have to implement this to every variable and then save.. So, is there a better solution with pure annotations / getters-setters ?

How to change Hibernate Mapping , without crashing the Database

I have two Entitys.
#Entity
#Table(name = "EX.EXAMPLE")
public class Entity
{
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private long id;
#OneToOne
private Entity2 stuff1;
#OneToOne
private Entity2 stuff2;
#OneToOne
private Entity2 stuff3;
}
And the second is the following
#Entity
#Table(name = "EX.EXAMPLE2")
public class Entity2
{
#Id
private long stuff;
}
Know i need to change the current id (Stuff) to a new One. I added a new columne ID to table. How to change/migrate the foreign Keys and make the new Columne ID a uniqe Key ?
Is it possibe to do it with Hibernate ? or is there a way with MYSQL ?
you are not allowed to modify the primary key. do it in a different approach. and why you have created three #OneToOne relations with the same entity? this is redundant and they are the same. it's not necessary to make relation for each object

Cascade persist creates duplicate rows?

I'm creating a database entity object Order, and assign it to multiple entities of type BookingCode.
Problem: this creates a single order in db, which is fine. But the order itself has a #OneToOne OrderDescription, which occurs duplicate in the database.
#Entity
public class BookingCode {
#Id
private Long id;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.DETACH})
private Order order;
}
#Entity
public class Order {
#Id
private Long id;
private String orderName;
#OneToOne(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private OrderDescription description;
}
#Entity
public class OrderDescription {
#Id
private Long id;
//for simplicity just one text element; of course multiple fields in real life
private String text;
#OneToOne
private Order order;
}
Test:
Order order = new Order();
order.setOrderName("test");
OrderDescription d = new OrderDescription("testdescr");
d.setOrder(order);
order.setDescription(d);
List<BookingCodes> codes = new ArrayList<>();
BookingCode code = new BookingCode();
code.setOrder(order);
codes.add(order);
BookingCode code2 = new BookingCode();
code2.setOrder(order); //using the same offer entity!
codes.add(order2);
codes = dao.save(codes); //CrudRepository from Spring
dao.findOne(codes.get(0).getId()); //this works, find an order which has one of the OrderDescriptions
Result:
In my database I then have two OrderDescription entries, where I would expect only one, because I reused the same Order object and assigned it to different BookingCode objects.
Like:
table order_descrption:
1;"de";"testdescr";"123456"
2;"de";"testdescr";"123456"
As Order has a #OneToOne relation to OrderDescription
And I even don't understand why the select using findOne() works correctly. Because in database I now have two OrderDescriptions that map to the same Order, but an Order can only have one of them.
Persist the order first and then assign it to both bookingCode .
I had a similar issue where I had an Order obj and its variable prevOrder was referring to itself i.e. Order entity. And when I stored order, it would end up storing duplicate records for prevOrder.
I had the following code:
#Entity
#Table(name = "orders")
public class Order implements Serializable {
#Id
#GeneratedValue(generator = "order_id_generator")
#SequenceGenerator(name = "order_id_generator", sequenceName = "order_id_sequence", allocationSize = 1)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToOne(cascade = CascadeType.ALL, optional = true)
#JoinColumn(name = "previous_order_id", unique = true, updatable = false, referencedColumnName = "id")
private Order previousOrder;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "previousOrder")
private Order nextOrder;
...
I tried various things including overriding equals and hashcode of Order, and adding a OneToOne mappedBy field 'nextOrder' etc. But noticed JPA didn't even call equals() to determine object's uniqueness. Ultimately I found out that JPA uses id field as the object's identifier and I wasn't storing the generated id while storing the object to a distrobuted cache. So it was all the time creating fresh objects during persistence.

Hibernate avoid nullable unique fields in query?

I am having one hibernate pojo class which has 3 fields specified in #UniqueConstraint (unique together) where one of these 3 fields is nullable=true.
When I try to update entry with session.update(pojo) it updates all the entries in database which matches 2 fields (which are not nullable), so does hibernate avoid nullable fields while querying? or there is something what I should know about it?
Edit: Added class
#Entity
#Table (name = "details",
uniqueConstraints = {#UniqueConstraint(columnNames = {"service_id", "billing_item_id", "service_type_id"}, name="UK_name_it")}
)
public class Detail implements Serializable {
#ManyToOne
#JoinColumn(name = "service_id")
#ForeignKey(name = "FK_name2")
#Id
private Service service;
#ManyToOne
#JoinColumn(name="billing_item_id")
#ForeignKey(name = "FK_name3")
#Id
private BillingItem billingItem;
#ManyToOne
#JoinColumn(name="currency_id")
#ForeignKey(name = "FK_name4")
private Currency currency;
#ManyToOne
#JoinColumn(name="service_type_id")
#ForeignKey(name = "FK_name5")
private ServiceType serviceType;
#Column(name = "completed", nullable = false)
private boolean completed;
}
There doesn't seem to be any option like that to have a nullable field in composite key, so I had to end up by adding a integer autoincrement primary key to the table, and keeping service, billingItem and serviceType fields in #UniqueConstraint.
There is another option I could adopt, which is possible in certain scenarios, by adding a serviceType which is considered as All entry (basically when serviceType is null it applies to all the serviceTypes.) and instead of using null for serviceType point to this entry, this way we can have PK and no need to make serviceType a nullable field.

can someone please explain me #MapsId in hibernate?

Can someone please explain to me #MapsId in hibernate? I'm having a hard time understanding it.
It would be great if one could explain it with an example and in what kind of use cases is it most applicable?
Here is a nice explanation from Object DB.
Designates a ManyToOne or OneToOne relationship attribute that provides the mapping for an EmbeddedId primary key, an attribute within an EmbeddedId primary key, or a simple primary key of the parent entity. The value element specifies the attribute within a composite key to which the relationship attribute corresponds. If the entity's primary key is of the same Java type as the primary key of the entity referenced by the relationship, the value attribute is not specified.
// parent entity has simple primary key
#Entity
public class Employee {
#Id long empId;
String name;
...
}
// dependent entity uses EmbeddedId for composite key
#Embeddable
public class DependentId {
String name;
long empid; // corresponds to primary key type of Employee
}
#Entity
public class Dependent {
#EmbeddedId DependentId id;
...
#MapsId("empid") // maps the empid attribute of embedded id
#ManyToOne Employee emp;
}
Read the API Docs here.
I found this note also useful: #MapsId in hibernate annotation maps a column with another table's column.
It can be used also to share the same primary key between 2 tables.
Example:
#Entity
#Table(name = "TRANSACTION_CANCEL")
public class CancelledTransaction {
#Id
private Long id; // the value in this pk will be the same as the
// transaction line from transaction table to which
// this cancelled transaction is related
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_TRANSACTION", nullable = false)
#MapsId
private Transaction transaction;
....
}
#Entity
#Table(name = "TRANSACTION")
#SequenceGenerator(name = "SQ_TRAN_ID", sequenceName = "SQ_TRAN_ID")
public class Transaction {
#Id
#GeneratedValue(generator = "SQ_TRAN_ID", strategy = GenerationType.SEQUENCE)
#Column(name = "ID_TRANSACTION", nullable = false)
private Long id;
...
}
IMHO, the best way to think about #MapsId is when you need to map a composite key in a n:m entity.
For instance, a customer can have one or more consultant and a consultant can have one or more customer:
And your entites would be something like this (pseudo Java code):
#Entity
public class Customer {
#Id
private Integer id;
private String name;
}
#Entity
public class Consultant {
#Id
private Integer id;
private String name;
#OneToMany
private List<CustomerByConsultant> customerByConsultants = new ArrayList<>();
public void add(CustomerByConsultant cbc) {
cbc.setConsultant(this);
this.customerByConsultant.add(cbc);
}
}
#Embeddable
public class CustomerByConsultantPk implements Serializable {
private Integer customerId;
private Integer consultantId;
}
#Entity
public class CustomerByConsultant{
#EmbeddedId
private CustomerByConsultantPk id = new CustomerByConsultantPk();
#MapsId("customerId")
#JoinColumn(insertable = false, updatable = false)
private Customer customer;
#MapsId("consultantId")
#JoinColumn(insertable = false, updatable = false)
private Consultant consultant;
}
Mapping this way, JPA automagically inserts Customer and Consultant ids in the EmbeddableId whenever you save a consultant. So you don't need to manually create the CustomerByConsultantPk.
As he explained Vladimir in his tutorial, The best way to map a #OneToOne relationship is to use #MapsId. This way, you don’t even need a bidirectional association since you can always fetch the Child entity by using the Parent entity identifier.
MapsId lets you use the same primary key between two different entities/tables. Note: when you use MapsId, the CASCADE.ALL flag becomes useless, and you will need to make sure that your entities are saved manually.

Categories