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.
Related
maybe duplicate question but I couldn't fina a solution for my case which I think is pretty simple.
I have two tables like so :
And those are the related DTO Object :
First table
#Entity
#Table(name = "DA10003_REF_SIGNALEMENT")
public class RefSignalement {
#Id
#Column(name = "CD_SIGNALEMENT")
public String codeSignalement;
#Column(name = "LIBELLE")
public String libelle;
#Column(name = "CATEGORIE")
public String categorie;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "CD_SIGNALEMENT")
public List<RefMessage> refMessages;
}
Second table :
#Entity
#Table(name = "DA10004_REF_MESSAGE")
public class RefMessage {
#Id
#Column(name = "CD_SIGNALEMENT")
public String codeSignalement;
#Id
#Column(name = "DESTINATAIRE")
public String destinataires;
#Column(name = "MESSAGE")
public String message;
}
And the following query to get all the RefSignelement with the associated message :
List<RefSignalement> listRefSignalement = em.createQuery("SELECT p FROM RefSignalement p, RefMessage m", RefSignalement.class).getResultList();
Unfortunately it's returning an empty list, I have tried to change it with join fetch but nothing change.
Thank for the help
Remember that in JPQL you have to think in Objects, not relations. You want to fetch all 'RefSignalement' and eagerly fetch their 'refMessages' properties:
SELECT DISTINCT s FROM RefSignalement s JOIN FETCH s.refMessages
Here the "distinct" is only needed by JPA when assembling your resulting entities, but add unnecessary overhead to the SQL Query. If you have a Hibernate version >= 5.2.2 (I think), then there is a query hint you can use to avoid that:
List<RefSignalement> sigs = entityManager
.createQuery(
"select distinct s " +
"from RefSignalement s " +
"left join fetch s.refMessages ")
.setHint("hibernate.query.passDistinctThrough", false)
.getResultList();
Read more about it here.
a couple of things, RefMessage class is using composite primary key so i guess you need to use #IdClass or #EmbeddedId annotation. here I'm providing using
#IdClass
public class RefId implements Serializable {
private String codeSignalement;
private String destinataires;
// default constructor
public RefId() {
}
public RefId(String codeSignalement, String destinataires) {
this.codeSignalement = codeSignalement;
this.destinataires = destinataires;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RefId refId = (RefId) o;
return Objects.equals(codeSignalement, refId.codeSignalement) &&
Objects.equals(destinataires, refId.destinataires);
}
#Override
public int hashCode() {
return Objects.hash(codeSignalement, destinataires);
}
}
then you need to use like follows
#Entity
#Table(name = "DA10004_REF_MESSAGE")
#IdClass(RefId.class)
public class RefMessage {
#Id
#Column(name = "CD_SIGNALEMENT")
public String codeSignalement;
#Id
#Column(name = "DESTINATAIRE")
public String destinataires;
#Column(name = "MESSAGE")
public String message;
}
define your repository as follows:
public interface RefSignalementRepo extends
JpaRepository<RefSignalement, String> {
}
RefSignalement class defination as follows:
#Entity
#Table(name = "DA10003_REF_SIGNALEMENT")
public class RefSignalement {
#Id
#Column(name = "CD_SIGNALEMENT")
public String codeSignalement;
#Column(name = "LIBELLE")
public String libelle;
#Column(name = "CATEGORIE")
public String categorie;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinColumn(name = "CD_SIGNALEMENT")
public List<RefMessage> refMessages;
}
very example app
#SpringBootApplication
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(App.class, args);
RefSignalementRepo repo = context.getBean(RefSignalementRepo.class);
RefSignalement obj = new RefSignalement();
obj.codeSignalement = "1";
obj = repo.save(obj);
obj.refMessages = new ArrayList<>();
RefMessage message = new RefMessage();
message.codeSignalement = "1";
message.destinataires = "2";
message.message = "custom message";
obj.refMessages.add(message);
obj = repo.save(obj);
List<RefSignalement> objs = repo.findAll();
System.out.println(objs.get(0).refMessages.size());
EntityManager em = context.getBean(EntityManager.class);
List<RefSignalement> listRefSignalement = em.createQuery("SELECT p FROM RefSignalement p, RefMessage m", RefSignalement.class).getResultList();
System.out.println(listRefSignalement.get(0).refMessages.size());
}
}
I have two entities Train and Station. They have #OneToMany relation on Timetable entity. In Timetable entity I have composite key on Train and Station as #ManyToOne.
All classes have setters and getters
Train
#Entity
#Table(name = "train")
public class Train {
#Id
#GeneratedValue
private Long id;
#Column(name = "type")
private String type;
#OneToMany(mappedBy = "train",fetch = FetchType.EAGER)
#OrderBy("curentnumber ASC")
private List<Coach> coaches;
#OneToMany(mappedBy = "train",fetch = FetchType.LAZY)
#OrderBy("arrivalTime ASC")
private List<Timetable> timetable ;
...
}
Station
#Entity
#Table(name = "station")
public class Station {
#Id
#GeneratedValue
private Long id;
#Column(name = "name", length = 16, nullable = false)
private String name;
#OneToMany(mappedBy = "station", fetch = FetchType.LAZY)
private List<Timetable> timetable;
...
}
Timetable
#Entity
#IdClass(TimetableId.class)
#Table(name = "timetable")
public class Timetable implements Serializable, Comparable<Timetable>{
#Id
#ManyToOne
#JoinColumn(name = "train_id", referencedColumnName = "id")
private Train train;
#Id
#ManyToOne
#JoinColumn(name = "station_id", referencedColumnName = "id")
private Station station;
#Column(name = "arrivalTime")
private Timestamp arrivalTime;
#Column(name = "departureTime")
private Timestamp departureTime;
...
}
TimetableId
public class TimetableId implements Serializable{
Long train;
Long station;
public TimetableId() {
}
...
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TimetableId that = (TimetableId) o;
if (train != null ? !train.equals(that.train) : that.train != null) return false;
return station != null ? station.equals(that.station) : that.station == null;
}
#Override
public int hashCode() {
int result = train.hashCode();
result = 31 * result + station.hashCode();
return result;
}
}
I have service class too.
//Result timetable
Timetable addedTimetable = new Timetable();
addedTimetable.setTrain(new Train());
addedTimetable.getTrain().setId(Long.valueOf(trainid));
addedTimetable.setStation(new Station());
addedTimetable.getStation().setId(Long.valueOf(stationid));
try{
timetableRepository.create(addedTimetable);
} catch (Exception e) {
e.printStackTrace();
}
And some from GenericDAO
#Override
public void create(T entity) {
EntityManager manager = emf.createEntityManager();
try {
try {
manager.getTransaction().begin();
manager.persist(entity);
manager.getTransaction().commit();
}
finally {
if (manager.getTransaction().isActive())
manager.getTransaction().rollback();
}
} finally {
manager.close();
}
}
The problem is when I have #IdClass annotation and try to create new Timetable referenced on new Station() and new Train with existing id, then i get error for "detached entity passed to persist:entity.Station" in my dao level. But if i remove #IdClass annotation, then everything is good and Timetable creates correctly. I think that i make something wrong in TimetableId and annotations...
Please help me, i stack here.
I'm working on a project where I need to save a download file information with keywords.
Typical one-to-many scenario, right? A download record to many download-keyword relationship records.
So here is what the Download.java look like
#Entity
#Table(name = "downloads", catalog = "nbpx")
public class Download extends BaseEntity implements Serializable {
public Integer downloadId;
public String title;
public Set<DownloadKeyword> downloadKeywords = new HashSet<DownloadKeyword>(
0);
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "downloadId", unique = true, nullable = false)
public Integer getDownloadId() {
return downloadId;
}
public void setDownloadId(Integer downloadId) {
this.downloadId = downloadId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "download")
#Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
public Set<DownloadKeyword> getDownloadKeywords() {
return downloadKeywords;
}
public void setDownloadKeywords(Set<DownloadKeyword> downloadKeywords) {
this.downloadKeywords = downloadKeywords;
}
}
Since I don't want any duplicate download-keyword relation records in the table, I set a JPA unique Constraints for the class. But I didn't use a composite key. And this is what DownloadKeyword.java look like:
#Entity
#Table(name = "downloadkeywords", catalog = "nbpx", uniqueConstraints =
#UniqueConstraint(columnNames = {"keywordId", "downloadId" }))
public class DownloadKeyword extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
public Integer downloadKeywordId;
public Download download;
public Integer keywordId;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "downloadKeywordId", unique = true, nullable = false)
public Integer getDownloadKeywordId() {
return downloadKeywordId;
}
public void setDownloadKeywordId(Integer downloadKeywordId) {
this.downloadKeywordId = downloadKeywordId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "downloadId", nullable = false, insertable=true)
public Download getDownload() {
return download;
}
public void setDownload(Download download) {
this.download = download;
}
public Integer getKeywordId() {
return keywordId;
}
public void setKeywordId(Integer keywordId) {
this.keywordId = keywordId;
}
}
When I save a download entity, I expect to see all the download-keyword relationship entities are saved within the download entity save session. And I need to avoid duplication in download-keyword relationship table. But every time I save it, the relation records were inserted straightly into DB without checking the duplication with JPA annotation.
So here is my question: Did multiple-column uniqueConstraints of JPA annotation get ignored when entities were saving in one-to-many save or update session?
The uniqueConstraints are only for table generation support.
See the documentation.
Since you're already using Set<DownloadKeyword> in the Download class - you can implement hashCode and equals methods in the DownloadKeyword class (include primary key downloadKeywordId and the keywordId properties) to accomplish what you want.
However, I would still prefer a composite key approach.
I have those 2 tables Teacher and Contact, a teacher can have x Contacts. So here we are looking at a #OneToMany association.
Tables Structure:
User [userid, username, email,...]
Contact [contactid, contactname, ref, reftype,...]
I want to load from my User Class all the user's contacts. To do that I would do a query like
Select * from contact as c WHERE c.ref=8240 AND c.reftype='T';
8240 being a random userid and reftype T being for Teacher. As this contact table is used as well for school contacts and/or anyother type of customer we could have. The problem is I have no idea how to do this with Hibernate. Should I use embedbedId? Or a JoinColumns?
What I have done so far is to link my teacher to contacts having contact.ref=teacher.teacherid but what I want is :
contact.ref=teacher.teacherid AND contact.reftype='T'
How do I do that?
Here is my code
Teacher.class
private Integer teacherid;
private Set<Contact> contact;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "teacherid", unique = true, nullable = false)
public Integer getTeacherId() {
return teacherid;
}
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name="ref"),
})
public Set<Contact> getContact() {
return contact;
}
public void setContact(Set<Contact> contact) {
this.contact = contact;
}
Contact.class
#Entity
#Table(name = "contact")
public class Contact implements java.io.Serializable {
private Integer contactid;
private String contactname;
private String contacttype;
private String reftype;
private int ref;
/*private Teacher teacher;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "ref"),
#JoinColumn(name = "reftype")
})
public Teacher getTeacher() {
return teacher;
}
public void setTeacher (Teacher teacher) {
this.teacher= teacher;
}
*/
private Set<ContactItem> contactItems;
private Set<ContactAddress> contactAddressess;
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="contactid")
public Set<ContactItem> getContactItems(){
return contactItems;
}
public void setContactItems(Set<ContactItem> contactItems) {
this.contactItems = contactItems;
}
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="contactid")
public Set<ContactAddress> getContactAddressess(){
return contactAddressess;
}
public void setContactAddressess(Set<ContactAddress> contactAddressess) {
this.contactAddressess = contactAddressess;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "contactid", unique = true, nullable = false)
public Integer getContactid() {
return this.contactid;
}
public void setContactid(Integer contactid) {
this.contactid = contactid;
}
#Column(name = "contactname", nullable = false)
public String getContactname() {
return this.contactname;
}
public void setContactname(String contactname) {
this.contactname = contactname;
}
#Column(name = "contacttype", nullable = false)
public String getContacttype() {
return this.contacttype;
}
public void setContacttype(String contacttype) {
this.contacttype = contacttype;
}
#Column(name = "reftype", nullable = false, length = 1)
public String getReftype() {
return this.reftype;
}
public void setReftype(String reftype) {
this.reftype = reftype;
}
#Column(name = "ref", nullable = false)
public int getRef() {
return this.ref;
}
public void setRef(int ref) {
this.ref = ref;
}
public String toString(){
return "\n#"+this.contactname+" : ("+this.ref+"-"+this.reftype+") \n"
+"#Items-----\n"+getContactItems()+"\n"
+"#Address---\n"+getContactAddressess()+"\n";
}
}
Assuming that Teacher is a User, and that every user has contacts.
User.class
#OneToMany(mappedBy = "user", targetEntity = Contact.class, orphanRemoval=true)
#Cascade(CascadeType.ALL)
private Set<Contact> contacts = new ConcurrentSkipListSet<Contact>();
//No setContacts here.
Contact.class
#ManyToOne
private User user;
public void setUser(User user){
this.user = user;
}
That's it.
First, since there's a User table and no Teacher table (teachers seem to be a sub-set of user rows, denoted by a 'type' column) I wouldn't have a table of User and a Teacher model. I would have only a User model instead. Hibernate is much easier if you do things the Hibernate way, which is one model per table with the model having the same name. For example, if you do this, you can use a tool to auto-generate (reverse engineer) all your model classes. This means a Hibernate tool will look at your tables, foreign keys, etc and generate appropiate Java code for your tables. Very very convenient when you start making table changes.
Normally you'll reverse engineer the model classes. Since these are machine-generated you don't want to change them because the changes will be over-written the next time to reverse-engineer the models. What I do for conditions such as yours is to create a class called a DAO - Data Access Object, or DAO.
public class UserDAO {
public static User getTeacher(EntityManager em, Long id) {
try {
IForgotTheType query = em.createQuery("User from User user, Contact contact where contact.ref=user.teacherid AND contact.reftype='T' and User.id=:id");
query.setParameter("id", id);
return (User) query.getSingleResult();
} catch (NoResultException e) {
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Obviously I am not sure about your table structure and column names but you get the idea. You can see where I inserted your code into the query above. Now you can get a teacher by simply calling UserDAO.getTeacher(). Use DAOs - otherwise you'll have Hibernate code everywhere in your code making maintenance more difficult.
Check out section 3.4 of this.
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);