I have two tables in database, A and B. Table B has an id composed of two fields. One of them is a foreign key to A. Id of A is automatically generated on insert by a sequence.
A:
ID (PK)
(*other fields*)
B:
SOME_FIELD (PK)
A_ID (PK, FK to A)
I have mapped the two tables in JPA (Hibernate) following JPA specification, in this way:
#Entity
#Table(name = "A")
public class A implements Serializable {
#Id
#SequenceGenerator(name = "A_SEQ", sequenceName = "A_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "A_SEQ")
#Column(name = "ID")
private Long id;
(...)
}
#Entity
#Table(name = "B")
public class B implements Serializable {
#EmbeddedId
#AttributeOverride(name = "someField", column = #Column(name = SOME_FIELD))
private BPK pk;
#MapsId("aId")
#ManyToOne
#JoinColumn(name = "A_ID")
private A a;
(...)
}
#Embeddable
public class BPK implements Serializable {
private Long aId;
private String someField;
#Override
public boolean equals(Object o) {
(...)
}
#Override
public boolean hashCode() {
(...)
}
(...)
}
The problem is that when I try to save an B object calling entityManager.persist(b), where b.a is set to an A object, which exists already in database, I get an exception:
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: package.name.A
I don't know why this happens. I'm trying to save object of class B, not A. Is my entity class wrong? Or maybe I shouldn't use persist here?
It could be that the entity A is no longer being held by entity manager. Have you tried setting B.a with a "fresh" instance of A?
b.setA(get(b.a));
entityManager.persist(b);
The get(b.a) method can be whatever you usually use to find entity A from your datasource e.g. entityManager.getReference(A.class, a.id);
Related
I'm having three entities which are being saved into the DB. For instance we have 3 entities A, B and C. A is having OneToOne mapping with B and ManyToOne mapping with C which is uni-directional.
public class A {
#Id
#GeneratedValue
private int id;
private String number;
//Other fields declared
#PostPersist
private void onSaving(){
number = "STU" + id;
}
}
public class B {
#Id
#GeneratedValue
int otherID; //primary key for B
//Other fields declared
#OnetoOne(targetEntity = A.class, cascade= CascadeType.ALL)
#JoinColumn(name = "id") // id is the primary key of class A
private A a;
}
public class C {
#Id
#GeneratedValue
int otherID; //primary key for C
//Other fields declared
#ManytoOne
#JoinColumn(name = "id") // id is the primary key of class A
private A a;
}
Below is the service class for the above entities
#Service
public class testService {
#Autowired
private BRepo bRepo; // BRepo is the interface which is extending the JPARepository.
#Autowired
private CRepo cRepo; // CRepo is the interface which is extending the JPARepository.
#Transactional
public A saveEntity(B b, List<C> c) {
bRepo.save(b);
cRepo.saveAll(c);
return b.getA();
}
}
The entity fields/data are getting perfectly saved into the DB, the only issue I'm facing is with the #PostPersist annotation. My requirement is when save is performed, the number field/variable of entity A should be saved as "STU" + id(primary key for A), but in the DB it is saving as STU0 every time I perform save.
I'm unable to find the root cause for this over the internet. Requesting you to please help me debug this issue. Thank you.
The issue got resolved after I added, strategy as IDENTITY
#GeneratedValue(strategy = GenerationType.IDENTITY)
Customer said no Primary Key Required in Child Table. So Child table has two column "ID" and "Value" where ID can be duplicated.
When i remove #Id then hibernate says "No identifier specified for entity"
When i keep #Id in code then hibernate says "javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session" ; while persisting
So crux is that i need to keep #Id but how to persist two same ID in one session with #Id annotation.
Following is code:
Main Entity:
public class CustomerAgreement implements Serializable {
#OneToMany(mappedBy = "customerAgreement", orphanRemoval = true, fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST})
private List<CustomerAgreementComputerAttachments> autoAttachComputersFromOrganizations;
Composed Entity:
public class CustomerAgreementComputerAttachments implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "ID")
private CustomerAgreement customerAgreement;
Main Program:
public static List<CustomerAgreement> create() {
List<CustomerAgreement> li = new ArrayList<CustomerAgreement>();
CustomerAgreement cAgreement = new CustomerAgreement();
cAgreement.setId(2222l);
cAgreement.setName("Tillu");;
cAgreement.setCustomerId("140");
List<CustomerAgreementComputerAttachments> catl = new ArrayList<>();
CustomerAgreementComputerAttachments catt = new CustomerAgreementComputerAttachments();
catt.setAttachmentValue("TEST");
catt.setCustomerAgreement(cAgreement);
CustomerAgreementComputerAttachments tatt = new CustomerAgreementComputerAttachments();
tatt.setAttachmentValue("TESTy");
tatt.setCustomerAgreement(cAgreement);
catl.add(catt);
catl.add(tatt);
cAgreement.setAutoAttachComputersFromOrganizations(catl);
li.add(cAgreement);
return li;
}
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("IntegratorMasterdataPU");
em = emf.createEntityManager();
em.getTransaction().begin();
for(CustomerAgreement ca: create()) {
em.persist(ca);
}
em.getTransaction().commit();
}
An Entity must be identifiable by a unique key. This doesn't need to correspond to any database primary key but there must a unique column or columns that can be used to identity an entity.
If this is not possible, then you need to make CustomerAgreementComputerAttachment an #Embeddable.
An #Embeddable unlike an entity has no independent identity (no #ID). See further here:
What is difference between #Entity and #embeddable
#Entity
public class CustomerAgreement {
#ElementCollection
#JoinTable(name="...", joinColumn = "id")
private List<CustomerAgreementComputerAttachment> attachments;
}
and
#Embeddable
public class CustomerAgreementComputerAttachments {
//No back reference to CustomerAgreement
//Other fields as required.
}
I created an instance of A, defined name, with a blank collection of entities B and save it into DB. This is revision #1. Now I use the following statement to get all initial revision of class A
//Get revisions
A a = auditReader.find(A.class, aId, revisions.get(0));
I am getting an exception
could not resolve property: aId_id of: .B_AUDIT [select e__ from B_AUDIT e__ where e__.aId_id = :a_id and e__.originalId.REV.id <= :revision and REVTYPE != :delrevisiontype and (e__.REVEND.id > :revision or e__.REVEND is null)]
Following are my class details
#Table(name = "A")
#Audited
public class A{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
....
#OneToMany(mappedBy = "aId")
#AuditMappedBy(mappedBy = "aId")
private List<B> b;
}
which has #oneToMany relationship with B
#Entity
#Table(name = "B")
#Audited
public class B{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int aId;
...
}
Hibernate Enver Version : 5.1.4.Final
Thank you for your support.
If I had to wager a guess, I believe it is likely because of how you decided to map the oppsite side of the #OneToMany relationship inside entity B. You mapped it directly to the primary key value rather than to the entity type itself.
In other words, Envers likely expected this mapping instead:
#Entity
#Table(name = "B")
#Audited
public class B {
// other stuff removed for breavity
#ManyToOne
private A a;
}
I have 2 entities O and D with a one to many relationship from O (one) to D (many). The relationship itself has an attribute - count.
What is the best way to model this using hibernate?
What I have currently is another entity OD representing the relationship. It has its own artificial key
The abbreviated version of the entities is as below
#Entity
class O {
#Id
private Long id;
#OneToMany(mappedBy = "o")
private Set<OD> ods;
}
#Entity
class OD {
#Id
private Long id;
#ManyToOne
private O o;
// This is uni-directional reln
#OneToOne
private D d;
private int count;
}
Is this the best way? I do not like the fact that the relationship has its own id, but is there a better way to model this relationship?
You can model the OD as #Embeddable, and change the owning side from #OneToMany to #ElementCollection e.g.
#Entity
public class O {
#Id
private Long id;
#ElementCollection
#CollectionTable( name = "OD")
private Set<OD> ods;
}
#Embeddable
public class OD {
// This is uni-directional reln
#OneToOne
private D d;
private int count;
}
The DDL statements would be almost the same with the distinction that the life-cycle of the OD would always be dependent on the life of its parent object, and would not hold an identity of its own
my problem is that I cannot save my entity because it contains another entity, mapped by a key that is also a part of this table's primary key. The table looks like this:
table C:
+-----+------+
| id_A | id_B |
+-----+------+
..where idA is the primary key of table A with EntityA and idB the primary key of table B with EntityB.
so its basically a n-to-m relation. This is the entity I'm using for table C:
#Entity
public class EntityC {
private long idA;
private EntityB b;
#Id
#Column(name = "id_A")
public long getIdA() {
return idA;
}
#Id
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_B")
public EntityB getB() {
return b;
}
...setters are here...
}
Please note that id_A is mapped as is (the id), while id_B is mapped as its object representation, EntityB. This is what I want to do with it:
EntityC c = new EntityC();
c.setIdA(123);
c.setB(new EntityB());
em.persist(c);
tx.commit();
em.close();
I want to persist EntityB ONLY IF I can persist EntityC.
on tx.commit() I get this exception: org.hibernate.TransientObjectException: object references an unsaved transient instance
I suppose this happens because part of the primary key, id_B, is not saved. But i set cascading to all so there should be no problem!
Why is this not working?
EDIT:
When I do this:
em.persist(c.getB());
em.persist(c);
it works. But can't Hibernate/JPA do that automatically? I thought that's what cascading is good for.
EDIT2:
added an embeddedId instead of id_A and id_B:
#Embeddable
public class EntityCID implements Serializable {
public long idA;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_B", referencedColumnName = "id")
public EntryB b;
}
EntityC now looks like:
#Entity
public class EntityC implements Serializable {
private EntityCID id;
...
#EmbeddedId
public void getId() {
return id;
}
}
but I still get the transient object exception if I don't em.persist(c.getId().b); before em.persist(c). Sticking to that, although it is ugly.
#Trein: it is not bidirectional. EntityB code:
#Entity
public class EntityB implements Serializable {
public long id;
public String text;
}
If you think about it what you are seeing makes perfect sense.
EntityC is is the 'owning side' of the relationship C<>B: it defines the JoinColumn and EntityB has the 'mappedBy' attribute.
So on saving C, order of events would normally be:
insert into C/update C
insert into B/update B
Now in your case this causes issues as obviously C can only be saved if B has been persisted first.
In terms of your statement above: I want to persist "EntityB ONLY IF I can persist EntityC." How can this ever be the case?
JPA has a concept of 'Derived Identifiers', which I am not overly familiar with however is defined in the book Pro JPA as occurring when:
When an identifier in one entity includes a foreign key to another
entity, we call it a derived identifier. Because the entity containing
the derived identifier depends upon another entity for its identity,
we call the first the dependent entity. The entity that it depends
upon is the target of a many-to-one or one-toone relationship from the
dependent entity, and is called the parent entity
Now, despite the original advice that you had two #Id attributes defined and this was wrong it would however appear that having an additional #Id on a 1-2-m is in fact valid in JPA 2 for precisely this case.
The book gives a number of ways of dealing with Derived Identifiers however one example given below looks fairly similar to your case. So you may want to investigate further the #MapsId attribute.
#Entity
public class Project {
#EmbeddedId private ProjectId id;
#MapsId("dept")
#ManyToOne
#JoinColumns({
#JoinColumn(name="DEPT_NUM", referencedColumnName="NUM"),
#JoinColumn(name="DEPT_CTRY", referencedColumnName="CTRY")})
private Department department;
// ...
}
#Embeddable
public class ProjectId implements Serializable {
#Column(name="P_NAME")
private String name;
#Embedded
private DeptId dept;
// ...
}
See further:
How do I properly cascade save a one-to-one, bidirectional relationship on primary key in Hibernate 3.6
Is it a bidirectional relationship? I would suggest you to remove #Id getB() and perform the modifications:
#OneToOne(cascade = CascadeType.ALL, mappedBy = "id_B")
#PrimaryKeyJoinColumn(name = "id_B")
public EntityB getB() {
return b;
}
Your entity class must have only one attribute annotated with #Id. Usually when you need this, you create a class that will store both properties and this will act as a Id Class.
You can not pass new Entity() for reference. Because it won't have any values in it(even primary key). So how can hibernate will insert it as foreign key to the table. And cascade will save your parent object if its not saved,no need to call save method for all. But when you passing new object it won't do.