ORA-01400: Error while inserting Foreign Key using Hibernate - java

I have two tables A & B with a foreign key from B to a called FK_A. I have a not null constraint on FK_A.
I have the following two classes:
#Entity
#Table(name = "A")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "ID")
private long id;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name = "FK_A", nullable = true)
private Set<B> b;
//getters setters etc.
}
#Entity
#Table(name = "B")
public class B {
//attributes, getters setters etc.
}
The reads work fine but when I try to write A to the DB, I get : ORA-01400: cannot insert NULL into ("SCHEMA"."B"."FK_A")
The code I am tring to insert the entity looks something like:
#PersistenceContext(unitName = "ab")
private EntityManager em;
A a = new A();
B b = new B();
Set<B> bList = new HashSet();
bList.add(b);
a.setB(bList);
em.persist(a);
Now if I am correct shouldn't hibernate automatically populate FK_A in table B based on the id it had autogenerated. If not how can I go about setting it?

Take a look at mappedBy OneToMany.mappedBy(). JavaDoc:
The field that owns the relationship. Required unless the relationship is unidirectional.
It is a synonym for the Hibernate's keyword "inverse".
See also this question.
In addition as a variant may be your class B has property for class A declared with #ManyToOne(optional = false) annotation.

Related

Flushing entity A and B, when B's primary key is also A's? In JPA & Hibernate

I have the following entities A and B
#Entity
#Data
#Accessors(chain = true)
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "aId")
private long aId;
#OneToOne(mappedBy="a", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private B b;
#Entity
#Data
#Accessors(chain = true)
public class B {
#Id
#Column(name = "bId")
/**
*
* This is the same id as A.
*/
private long bId;
#OneToOne
#JoinColumn(name = "bId")
private A a;
How I am flushing entity A and B
A aEntity = new A();
B bEntity = new B();
aEntity.setbEntity(bEntity);
this.entityManager.persist(A);
this.entityManager.persist(B);
this.entityManager.flush();
I am trying to save both these entities in a transaction and I am having issues where B's id is not getting hydrated down by A's id.
On A.b you have a mappedBy="a", that means that B.a is the "owner" of the relation.
So you should set B.a instead of A.b to persist the relation:
B bEntity = new B();
A aEntity = new A();
bEntity.setbEntity(aEntity);
this.entityManager.persist(A);
this.entityManager.persist(B);
this.entityManager.flush();
If you need to keep working with those entities on the same transaction, calling entityManager.refresh on a entity will make sure that all properties are in sync.

Can I define different type for foreign key column from the parent table?

I need to connect to a postgreSQL database in a relation like in the code.
The problem is in the database "id" in table "b" is "bigint" and "b_id" in table "a" is "integer", prod db is not in my control so i can't change it.
"b_id" in "a" is not defined as foreign key, thats probably why postgre did allow that.
As expected, hibernate throws "found [int4 (Types#INTEGER)], but expecting [integer (Types#BIGINT)]"
If i create tables via hibernate it defines "b_id" in table "a" as "bigint" as expected
What would be the best way of validating the schema without changing types in the database?
#Entity
#Table(name = "a")
public class A {
#ManyToOne
#JoinColumn(name = "b_id")
#NotNull
private B bId;
...
}
#Entity
#Table(name = "b")
public class B {
#Id
private Long id;
...
}
You can use columnDefinition element in your A class.
#Entity
#Table(name = "a")
public class A {
#ManyToOne
#JoinColumn(name = "b_id", columnDefinition = "int")
#NotNull
private B bId;
...
}

Hibernate - Persist #OneToOne with a composite key

I have the following "audit" table that can host audit information about any other class of my schema:
CREATE TABLE `audit` (
`table_name` varchar(45) NOT NULL,
`item_id` int(11) NOT NULL,
`version` int(11) NOT NULL,
`updated_at` datetime NOT NULL,
`updated_by` varchar(25) NOT NULL,
`comment` varchar(255) DEFAULT NULL,
PRIMARY KEY (`table_name`,`item_id`,`version`)
)
Then, I have different JPA entities in my schema like so:
#Entity(name = "EntityA")
#Table(name = "entity_a")
public class EntityA {
#Id
#GeneratedValue
private Long id;
private Long version;
// Other fields
#OneToOne(mappedBy = "id.item", targetEntity = EntityAAudit.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private EntityAAudit audit;
}
At the same time, I have an asbtract class Audit that is a super-class for multiple entity-specific Audit classes:
#MappedSuperclass
#Table(name = "audit")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "table_name", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorOptions(insert = true, force = true)
public abstract class AuditHistory {
// Some audit fields like the date and the author of the modification
}
#Entity(name = "EntityAAudit")
#Table(name = "audit")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorValue("entity_a")
#DiscriminatorOptions(insert = true, force = true)
public class EntityAAudit extends Audit {
#EmbeddedId
#JsonUnwrapped
private AuditedId id;
#Embeddable
public static class AuditedId implements Serializable {
#OneToOne
#JoinColumn(name = "item_id", nullable = false)
private EntityA item;
#Column(name = "version", nullable = false)
private Long version;
}
}
This mapping works when retrieving the entities and their audit information from the database, but not when inserting a new entity with the corresponding audit information:
EntityA entity = new EntityA();
entity.setVersion(/* ... */);
// Setting the other basic fields of the entity
EntityAAudit audit = new EntityAAudit();
// Setting the basic fields of the audit
entity.setAudit(audit);
entity = em.merge(entity);
I end up with the following exception:
org.hibernate.id.IdentifierGenerationException: null id generated for:class EntityAAudit
I have literally tried everything I could think of or find online, and in the end it always comes down to the same issue: Hibernate tries to insert my Audit object with empty values for the item_id and the version.
If I manually set my entity instance and the version as the id of the audit object like so:
EntityA entity = new EntityA();
entity.setVersion(/* ... */);
// Setting the other basic fields of the entity
EntityAAudit audit = new EntityAAudit();
// Setting the basic fields of the audit
audit.setId(new EntityAAudit.AuditedId());
audit.getId().setItem(entity);
audit.getId().setVersion(entity.getVersion());
entity.setAudit(audit);
entity = em.merge(entity);
Then I end up with the even more obscure error here:
Caused by: java.lang.NullPointerException
at org.hibernate.type.descriptor.java.AbstractTypeDescriptor.extractHashCode(AbstractTypeDescriptor.java:65)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:185)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:189)
at org.hibernate.type.EntityType.getHashCode(EntityType.java:348)
Note that I cannot change the structure of my database nor the version of Hibernate (5.1.0, I know some bugs are fixed in later versions that could solve my issue...).
Thanks a lot :)
You might try a "derived identity" mapping:
#Entity(name = "EntityAAudit")
#Table(name = "audit")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorValue("entity_a")
#DiscriminatorOptions(insert = true, force = true)
public class EntityAAudit extends Audit {
#EmbeddedId
#JsonUnwrapped
private AuditedId id;
#OneToOne
#JoinColumn(name = "item_id", nullable = false)
#MapsId("entityAId") // maps entityAId attribute of embedded id
private EntityA item;
#Embeddable
public static class AuditedId implements Serializable {
private Long entityAId; // corresponds to PK type of EntityA
#Column(name = "version", nullable = false)
private Long version;
}
}
Note the #MapsId annotation on EntityAAudit.item.
Also, you will need to explicitly set EntityAAudit.item and AuditedId.version. JPA does not magically determine and set any circular references for you.
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.

Hibernate join all fields

I am using Spring Data + Hibernate and have an entity with several #ManyToOne fields. Every field is retrieved by hibernate with separate select query.
I've already tried #Fetch(value = FetchMode.JOIN), but no use,
Example:
#Entity
public class MyEntity{
#Id
private Long id;
#JoinColumn(name = "A")
#Fetch(value = FetchMode.JOIN)
#ManyToOne
private A a;
#ManyToOne
#Fetch(value = FetchMode.JOIN)
#JoinColumn(name = "B")
private B b;
}
Hibernate will perform 3 db trips for MyEntity, A and B.
Is there a way to retrieve my entity and all of its fields with single select where A and B will be joined.?
Edit:
Entities A and B defined like this:
#Entity
public class A{
#Id
Long id;
String name;
}
Repository looks like this (Spring Data creates implementation):
public interface MyEntityRepository extends CrudRepository<MyEntity , Long> {}
And code that loads entity is pretty simple too:
myEntityRepository.findAll();
Edit 2:
I've just written QueryDSL query and for it only 1 select was executed. I'm happy with that at least.

find(Class,id) and JPQL get different object (JPA)

I am using Hibernate 4.1.10.Final as my JPA provider, Spring and Spring MVC. There are two entities:
#Entity
#Table(name = "a")
public class A {
#Id
private String id;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<B> bs;
}
#Entity
#Table(name = "b")
public class B {
#Id
private String id;
#ManyToOne
#JoinColumn(name = "fk_a_id")
private A a;
}
I need to get an A and it's bs, so I use the find(A.class,id) of EntityManager.
A a1 = em.find(A.class, id);
a1.getBs().size();
For which the result is: the size of bs is zero (which means that there is no associated B).
But I'm sure that there are many associated Bs in the database, and indeed the data can been loaded from database while checking via the console.
When I use Query:
Query query = em.createQuery("SELECT a FROM A AS a WHERE a.id = ?1",A.class);
query.setParameter(1, id);
A a= (A) query.getSingleResult();
a.getBs().size(); // = 22
I instead get a size = 22.
What's wrong?
Since you used the mappedBy property in your #OneToMany, the owner of the relation is B and not A. That's why when you load an instance of A, the corresponding Bs are not loaded. Try modifying your annotations with the following :
In class A :
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="fk_a_id")
private Set<B> bs;
In class B :
#ManyToOne
#JoinColumn(name = "fk_a_id", insertable=false, updatable=false)
private A a;

Categories