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.
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
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
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);
}
}
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);