Hibernate class with primary key that is also a foreign key - java

#Entity
#Table(name = "USER_DATA")
public class UserData {
Entity entity;
#OneToOne(fetch = FetchType.EAGER)
#PrimaryKeyJoinColumn(name="PK_FK_ENTITY")
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
public Entity getEntity() {
return entity;
}
public void setEntity(Entity entity) {
this.entity = entity;
}
}
Error given is "No identifier specified for entity". How can I specify that the entity field is both a primary and a foreign key? Note that there is no class hierarchy for 'UserData' here; it is just a single class. It just so happens that for every 'UserData' there will only be one 'Entity', hence we want to make it both a primary and a foreign key.

We have the same case in our application and it works with this (we annotate the properties not the getters, don't know if there is any difference):
public class UserData {
#Id
#Column(name="PK_FK_ENTITY")
private int id;
#OneToOne
#JoinColumn(name="PK_FK_ENTITY")
private Entity entity;
...
public UserData (Entity entity, ...) {
this.id = entity.getId();
...
}
...
}
Note that in the constructor you should set the id. Neither the id nor the entity should have a setter as it cannot change.
Also note that we don't use cascade in this case. We first save the Entity that has a generated id, and then UserData.

For one to one bidirectional mapping, just define the #MapsId annotation at the child entity.
#Entity
#Table(name = "USER_DATA")
public class UserData {
#OneToOne(cascade = CascadeType.ALL, mappedBy = "userData", orphanRemoval = true)
private Entity entity;
public void setEntity(Entity entity) {
this.entity = entity;
if (null != entity && entity.getUserData() != this) {
entity.setUserData(this);
}
}
}
#Entity
#Table(name = "ENTITY")
public class Entity {
#Id
private Long id;
#MapsId
#OneToOne
#JoinColumn(name = "user_data_id")
private UserData userData;
public void setUserData(UserData userData) {
this.userData = userData;
if (null != userData && userData.getEntity() != this) {
userData.setEntity(this);
}
}
}
For one to many unidirectional mapping, You have to use #ElementalCollection and #CollectionTable and annotate Entity.class with #Embeddable annotation
#Entity
#Table(name = "USER_DATA")
public class UserData {
#ElementCollection
#CollectionTable(name = "entity",
joinColumns = #JoinColumn(name = "user_data_id"),
uniqueConstraints = { #UniqueConstraint(columnNames
= { "user_data_id", "name" }) })
private final Set<Entity> entities = new LinkedHashSet<>();
public void setEntities(Set<Entity> entities) {
this.entities.clear();
if (null != entities) {
this.entities.addAll(entities);
}
}
}
#Embeddable
public class Entity {
#Column
#Access(AccessType.FIELD)
private String name;
}
Kindly refers to the following articles for better understanding:
1. #OneToOne with shared primary key using #PrimaryKeyJoinColumn http://vard-lokkur.blogspot.my/2011/05/onetoone-with-shared-primary-key.html.
#OneToOne with shared primary key using #MapsId http://vard-lokkur.blogspot.my/2014/05/onetoone-with-shared-primary-key.html

Related

Hibernate: Entity property from non-entity class

so I have an hibernate Entity called Appointment, in this entity I have a AppointNumber property which itself contains a number property which is a string.
When I persist my Appointment, I need the AppointmentNumber. I got it to work with #Embedded and #Embeddable the other day but this creates a join table Which I can't have.
I tried many other solutions to try and get it to work without join tables but I can't figure it out. (I get lots of ava.lang.IllegalStateException)
Can anyone help?
Thanks!
#Entity(name = "appointments")
public class Appointment {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#OneToOne(mappedBy = "number")
#Fetch(value = FetchMode.SELECT)
private AppointmentNumber appointmentNumber;
Appointment entity
AppointmentNumber, used in Appointment but should not be an entity
public class AppointmentNumber {
#OneToOne
#JoinColumn(name = "appointmentNumber", unique = true, nullable = false)
private String number;
You could do like this:
#Entity(name = "appointments")
public class Appointment {
///....
#Convert(converter = AppointmentNumberConverter.class)
private AppointmentNumber appointmentNumber;
///....
}
#Converter
public class AppointmentNumberConverter implements
AttributeConverter<PersonName, String> {
#Override
public String convertToDatabaseColumn(AppointmentNumber appointmentNumber) {
if (appointmentNumber == null) {
return null;
}
return appointmentNumber.getNumber();
}
#Override
public AppointmentNumber convertToEntityAttribute(String appointmentNumber) {
if (appointmentNumber == null) {
return null;
}
AppointmentNumber result = new AppointmentNumber();
result.setNumber(appointmentNumber);
return result;
}
}
Have a look at JPA Converter documentation.

How to fix "A different object with the same identifier value was already associated with the session" error when using one-to-one relationship

I'm trying to create a one-to-one relationship between 2 entities:
Project entity
#Entity
#Table(name = "projects")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(mappedBy = "project", cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
private CrawlerConfiguration crawlerConfiguration;
// Getters and setters ...
public void setCrawlerConfiguration(CrawlerConfiguration crawlerConfiguration) {
if (crawlerConfiguration == null) {
if (this.crawlerConfiguration != null) {
this.crawlerConfiguration.setProject(null);
}
} else {
crawlerConfiguration.setProject(this);
}
this.crawlerConfiguration = crawlerConfiguration;
}
CrawlerConfiguration entity
#Entity
#Table(name = "crawler_configurations")
public class CrawlerConfiguration {
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private Project project;
// Getters and setters ...
}
When the user creates a new project, a configuration should also be created for the project.
#Transactional
public Project createProject(Project project) {
project.setCrawlerConfiguration(new CrawlerConfiguration());
return projectRepository.save(project);
}
Unfortunately it results in the following exception:
javax.persistence.EntityExistsException: A different object with the
same identifier value was already associated with the session :
[com.github.peterbencze.serritorcloud.model.entity.CrawlerConfiguration#1]
What is the correct way to create the entities?
Try this
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private Project project;
In your CrawlerConfiguration entity

Spring data Repositiry does not check for isNew(entity) on classes that are marked to be cascaded under the main entity

I have a parent class (for ease of understanding and running on your locals i have simplified the class structure)
#Entity
#Table(name = "entity_a")
public class EntityA implements Serializable, Persistable<Integer> {
#Id
#Column(name = "id")
private Integer id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "entity_b_id")
private EntityB entityB;
/* Getter and Setter */
#Override
public boolean isNew() {
return isNew;
}
}
#Entity
#Table(name = "entity_b")
public class EntityB implements Serializable, Persistable<String> {
#Id
#Column(name = "id")
private String id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "entityB")
private EntityA entityA;
/* Getters and Setters */
#Override
public boolean isNew() {
return isNew;
}
}
When I try to persist the new entity EntityA using the following spring data repository:
public interface EntityARepository extends JpaRepository<EntityA, Integer> {
}
it only checks for isNew on EntityA but ignores the isNew on EntityB and I keep getting primary key constraint errors on the persist operation
The reason why I am implementing the Persistable interface is because the EntityB is actually something that can be repeated quite a few times so I would like to check for its existence and set the isNew boolean accordingly before its saved
How do I make the spring repository take into account the isNew property of the child class as well that is marked to be cascaded

Duplicate entries for #OneToOne entity?

I have a simple parent-child relation.
Problem: in my database there are sometimes multiple child elements that map to the same parent.
#Entity
public class PersonEntity {
#Id
private long id;
//assume every person can only have one car
#OneToOne(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
private CarEntity car;
private String name;
}
#Entity
public class CarEntity {
#Id
private long id;
#OneToOne
#JoinColumn(name = "fk_person_id", foreignkey = #ForeignKey("name = "fk_person"))
private PersonEntity person;
private String name;
}
Question: how can I happen that there are multiple car elements that map to the same person entity? How is it even possible that hibernate persists those elements? And where could they come from?
As a result, when I fetch those person entities having multiple car mappings, I'm getting of course an exception:
Caused by: org.hibernate.HibernateException: More than one row with the given identifier was found: 123, for class: CarEntity...
Sidenote: I would finally want to delete the duplicate CarEntity and only keep the most recent one. But how can I prevent this to happen in future?
Update:
#Transactional
public PersonEntity createUpdatePerson(PersonDTO dto) {
PersonEntity entity = dao.findByPersonName(dto.getName); //CrudRepository by spring
if (entity == null) {
entity = new Person();
}
mergeDTO(dto, entity); //copy and fill new values
if (person.getId() == null) dao.save(entity);
return entity;
}
private void mergeDTO(PersonDTO dto, PersonEntity entity) {
if (dto.getCar() != null) {
if (entity.getCar() == null) entity.setCar(new Car());
entity.getCar().setName(dto.getCar().getName());
entity.getCar().setPerson(entity);
}
}

JPA not saving foreign key to #OneToMany relation

I'm using Spring with Hibernate as a JPA provider and are trying to get a #OneToMany (a contact having many phonenumbers) to save the foreign key in the phone numbers table. From my form i get a Contact object that have a list of Phone(numbers) in it. The Contact get persisted properly (Hibernate fetches an PK from the specified sequence). The list of Phone(numbers) also gets persisted with a correct PK, but there's no FK to the Contacts table.
public class Contact implements Serializable {
#OneToMany(mappedBy = "contactId", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
private List<Phone> phoneList;
}
public class Phone implements Serializable {
#JoinColumn(name = "contact_id", referencedColumnName = "contact_id")
#ManyToOne
private Contact contactId;
}
#Repository("contactDao")
#Transactional(readOnly = true)
public class ContactDaoImpl implements ContactDao {
#Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void save(Contact c) {
em.persist(c);
em.flush();
}
}
#Controller
public class ContactController {
#RequestMapping(value = "/contact/new", method = RequestMethod.POST)
public ModelAndView newContact(Contact c) {
ModelAndView mv = new ModelAndView("contactForm");
contactDao.save(c);
mv.addObject("contact", c);
return mv;
}
}
Hopefully I got all of the relevant bits above, otherwise please let me know.
You have to manage the Java relationships yourself. For this kind of thing you need something like:
#Entity
public class Contact {
#Id
private Long id;
#OneToMany(cascade = CascadeType.PERSIST, mappedBy = "contact")
private List<Phone> phoneNumbers;
public void addPhone(PhoneNumber phone) {
if (phone != null) {
if (phoneNumbers == null) {
phoneNumbers = new ArrayList<Phone>();
}
phoneNumbers.add(phone);
phone.setContact(this);
}
}
...
}
#Entity
public class Phone {
#Id
private Long id;
#ManyToOne
private Contact contact;
...
}
In reply to Cletus' answer. I would say that it's important to have the #column annotation on the id fields, as well as all the sequence stuff. An alternative to using the mappedBy parameter of the #OneToMany annotation is to use the #JoinColumn annotation.
As a kinda aside your implementation of addPhone needs looking at. It should probably be something like.
public void addPhone(PhoneNumber phone) {
if (phone == null) {
return;
} else {
if (phoneNumbers == null) {
phoneNumbers = new ArrayList<Phone>();
}
phoneNumbers.add(phone);
phone.setContact(this);
}
}
If the Contact-Phone relationship is unidirectional, you can also replace mappedBy in #OneToMany annotation with #JoinColumn(name = "contact_id").
#Entity
public class Contact {
#Id
private Long id;
#OneToMany(cascade = CascadeType.PERSIST)
#JoinColumn(name = "contact_id")
private List<Phone> phoneNumbers;
// normal getter/setter
...
}
#Entity
public class PhoneNumber {
#Id
private Long id;
...
}
Similar in JPA #OneToMany -> Parent - Child Reference (Foreign Key)
I don't think the addPhone method is necessary, you only have to set the contact in the phone object:
phone.setContact(contact);
If you want your relationship unidirectional i.e. can navigate from Contact to Phone's only, you need to add
#JoinColumn(name = "contact_id", nullable = false)
Under your #OneToMany on your parent entity.
nullable = false IS VITAL if you want hibernate to populate the fk on the child table
Try this sample:
#Entity
public class Contact {
#Id
private Long id;
#JoinColumn(name = "contactId")
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Phone> phones;
}
#Entity
public class Phone {
#Id
private Long id;
private Long contactId;
}
In JPA this helped me
contact.getPhoneList().forEach(pl -> pl.setContact(contact));
contactRepository.save(contact);

Categories