Insert fails for #OneToMany Hibernate mapping with inheritance - java

I'm using Java and Hibernate 3.6.4.Final.
TABLE_B is a mapping table for a specific type of A entities (B) which have a OneToMany relationship with X entities.
I'm having problems inserting a B entity in the DB.
Reading of B entities from the DB is not a problem and the related X entities are correctly loaded as well.
DB model:
TABLE_A (id, active, info)
TABLE_B (a_id, x_id)
TABLE_X (id, info)
An "A" entity might have zero or more relations with X entities.
An "A" entity with one or more relations to Xs are called "B" entities and have their own behavior in the code.
(The class and table names has been changed for simplicity)
#Entity
#Table(name = "TABLE_A")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class A {
...
}
#Entity
#Table(name="TABLE_B")
#PrimaryKeyJoinColumn(name="A_ID", referencedColumnName = "ID")
public class B extends A implements Serializable {
#OneToMany(cascade={CascadeType.ALL})
#JoinTable(
name="TABLE_B",
joinColumns = #JoinColumn( name="A_ID"),
inverseJoinColumns = #JoinColumn( name="X_ID", insertable = true, nullable = false)
)
private List<X> Xs;
...
}
The log:
SQLStatementLogger - insert into TABLE_A (active, info) values (?, ?)
SQLStatementLogger - insert into TABLE_B (A_ID) values (?)
JDBCExceptionReporter - could not insert: [com.test.B] [insert into TABLE_B (A_ID) values (?)]
java.sql.SQLException: Field 'X_ID' doesn't have a default value
I would understand if the problem is caused by specifying the entity table as "TABLE_B" and using the same for the OneToMany join table but this is what my DB model looks like.
To me it seems like Hibernate first tries to just insert the inheritance and if that would work the next insert would be the mapping between B and X. The problem is that for me the inheritance and mapping table are the same.
How should I map my DB model correctly in Hibernate? Help is much appreciated.

You can't have TABLE_B as both 'entity' table (e.g. table to which B is mapped) and 'join' table (e.g. table that holds joins between B and X).
In the first case, TABLE_B needs to have at most one record for each TABLE_A record (that is, for those As that are also Bs); in the second case TABLE_B needs to have as many records as you have X elements in B's collection which presents an obvious contradiction.
What you can do, therefore, is either of the following:
Map your Xs collection without join table (#ManyToOne on X side; #OneToMany mappedBy="X" on B side). Your 'X_TABLE' will have to have an a_id (or b_id, whatever you call it) FK to owner.
Use another table (TABLE_B_X for B-to-X mapping)

Related

In which direction Hibernate orm-mapping works

I have a rather simple (probably) question, but somehow struggling to find an answer.
How does hibernate map nested entities to java objects? Does it starts its mapping from high-level entities and stops on encountering null-values in ResultSet, or it starts from the lowest-level entities and check all of the hierarchy?
The first path seems to be more natural, but yet I didn't find a concrete answer or any way to configure this
EDIT
By nested entities I suppose something like this:
#Entity
public class A {
public Long id;
public String foo;
}
#Entity
public class B {
public Long Id;
#OneToOne
#JoinColumn(name = "aId")
public A bar;
}
Where bar field is nested entity for B
One more edit
By ORM-mapping I meant mapping on select query call - so, if table B has no records and we execute any select-query to find entities of B, does Hibernate atempt to find and map entities of A ? Futhermore, if we have
#Entity
public class C {
public Long Id;
#OneToOne
#JoinColumn(name = "bId")
public B foobar;
}
and yet there are no records in table B, some in C and some in A, when we use any find methods for C, does Hibernate attempt to find and map entities of A?
Hibernate ORM won't create a proxy for *-to-one association that can be null (#OneToOne(optional=false)). If the element is missing in the db it will be null when the entity is created. So, using your example, C.foobar will be null if there is no row on the table B in the db. The same applies to the other *-to-one associations.
If the element is not optional, it might load it lazily and therefore assign a proxy to it.
To expand a bit about your question.
This is a one-to-one association. More in general, what you call nested is an association in Hibernate ORM. You can find all the details about these types of mapping in the Hibernate ORM documentation.
For your specific example:
#Entity
public class A {
#Id
#Column(name = "aId")
public Long id;
public String foo;
}
#Entity
public class B {
#Id
public Long id;
#OneToOne
#JoinColumn(name = "aId")
public A bar;
}
Hibernate ORM will create the following tables in Postgres:
create table A (aId int8 not null, foo varchar(255), primary key (aId))
create table B (Id int8 not null, aId int8, primary key (Id))
alter table if exists B add constraint FK3mifipyyn4ao31rn7kftqknuc foreign key (aId) references A
Basically:
Table A with columns aid and foo
Table B with columns id and aid
Table B column aid has a foreign constraint to Table A column aid
You can find all the details about one-to-one mapping in the Hibernate ORM documentation.
If you run the HQL query from B, Hibernate ORM will run the following SQL:
1. select * from B;
2. select * from A a.where a.aid = ?
if the HQL is from C
1. select * from C;
2. select * from B b left outer join A a on b.aId = a.aId where b.id=?;
In both cases, the second query only runs if there are results for the first one.
Keep in mind that this is a basic example and the type of queries executed
will change based on the configuration and the mapping of the entities.
You can actually tweak this behaviour with small changes to the mapping (Lazy/Eager fetching, optional, using bidirectional associations, ...) or the queries. I'm not going through all the possible mappings and you should write your own tests and check what SQL is logged.
Hibernate ORM might also skip some queries if some of the entities or the query has been cached.

#Where with #SecondaryTable does not work with Hibernate

There is a one-to-many relationship in my model, where the child entity is stored in two tables.
#Entity
#Table(name = "child1")
#SecondaryTable(name = "child2", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "id1", referencedColumnName = "id1"),
#PrimaryKeyJoinColumn(name = "id2", referencedColumnName = "id2")})
#Where(clause = "col1 is not null and col2 is not null")
#Data
#Immutable
public class Child implements Serializable {...}
Child entity is fetched eagerly together with Parent entity. Problem lies within #Where clause, which should reference columns from two tables: col1 is in table child1 and col2 is in child2. This throws the following error:
ERROR 12333 --- [nio-8183-exec-7] o.h.engine.jdbc.spi.SqlExceptionHelper : Column (col2) not found in any table in the query (or SLV is undefined).
java.sql.SQLException: null
...
Using only: #Where(clause = "col1 is not null") gives propper mapping and results in no error.
Using #Where(clause = "child1.col1 is not null and child2.col2 is not null") gives the following error:
Column (child1) not found in any table in the query (or SLV is undefined).
How can I make #Where work with two tables or is there any workaround?
There are some requirements though:
I'm using informix as an underlying database and have read-only access.
I know, that it can be solved by native SQL or even JPQL / criteria API and so on, but doing so would make me rewrite a lot of core. I want to avoid it.
This is due to the HHH-4246 issue.
A workaround would be to replace the #SecondaryTable with a #OneToOne association using #MapsId.
This way, the child2 table becomes the Child2 entity for which you can use #Where or #Filter.

Owner side with JoinColumns annotation

I have the following scenario:
I'm having troubles figuring out what the owner side is and why ?
Please can you help ?
public class Basket
{
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumns({#JoinColumn(name="BASKET", referencedColumnName="ID")})
public Set<Product> getProductList() {
return this.productList;
}
}
public class Product
{
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({#JoinColumn(name="BASKET", referencedColumnName="ID")})
public Basket getBasket() {
return this.basket;
}
}
Basket is the "One" side of the relationship and there are many products identified as Baskets. So the "one" is usually the parent code and the child records reference the parent. Usually the easiest way to understand these is to look at the database foreign key constraint (hopefully there is one) that connects the backing tables.
One client has One basket and One basket has One owner, so:
class Owner has basket in relation #OneToOne.
One basket has many products, so:
class Basket has List of products in relation #OneToMany.
Many Products has One basket so:
class Product has relation in #ManyToOne.
The extract from the JPA 2.0 specification might help you to understand the issue:
2.10.2Bidirectional ManyToOne / OneToMany Relationships
Assuming that:
Entity A references a single instance of Entity B.
Entity B references a collection of Entity A[21].
Entity A must be the owner of the relationship.
The following mapping defaults apply:
Entity A is mapped to a table named A.
Entity B is mapped to a table named B.
Table A contains a foreign key to table B. The foreign key column name is formed as the concatenation of the following: the name of the relationship property or field of entity A; "_"; the name of the primary key column in table B. The foreign key column has the same type as the
primary key of table B.
And in section 11.1.21 the following is stated:
If there is more than one join column, a JoinColumn annotation must be specified for each join column using the JoinColumns annotation. Both the name and the referencedColumnName elements must be specified in each such JoinColumn annotation.
In your case, there is only one join column. So you don't need to the #JoinColumns annotation. Just use#JoinColumn. The #JoinColumn annotation is used always on the owning side of the relationship, that is the #ManyToOne side in this case.

When Should I Use #JoinColumn or #JoinTable with JPA?

#JoinColumn gives an Entity a foreign key to another Entity whereas #JoinTable will list the relationship between all relationships between Entity A and Entity B. As far as I can tell, they both appear to do similar things. When should I use one or the other?
Let's say you have an entity A which has a #ManyToOne association ot an entity B
#JoinColumn will define the target table Foreign Key (e.g B_ID) while using the target Entity table (e.g. B).
#Entity
public class A {
private Long id;
#ManyToOne
#JoinColumn(name="B_ID")
private B b;
}
#JoinTable will use a separate table to hold the relationship between A and B.
#Entity
public class A {
private Long id;
#ManyToOne
#JoinTable(
name = "A_B",
joinColumns = #JoinColumn(name = "B_ID"),
inverseJoinColumns = #JoinColumn(name = "A_ID")
)
private B b;
}
This time neither A nor B contain any Foreign Key, because there's a separate table (e.g. A_B) to hold the association between A and B.
#JoinTable stores the id of both the table into a separate table while #JoinColumn stores id of the another table in a new column.
#JoinTable : This is the default type. Use this when you you need a more normalized database. ie. to reduce redundancy.
#JoinColumn : Use this for better performance as it does not need to join extra table.
one important difference: #JoinColumn always depends upon the context it is used:
If the join is for a OneToOne or ManyToOne mapping using a foreign key mapping strategy, the foreign key column is in the table of the
source entity or embeddable.
If the join is for a unidirectional OneToMany mapping using a foreign key mapping strategy, the foreign key is in the table of
the target entity.
If the join is for a ManyToMany mapping or for a OneToOne or bidirectional ManyToOne/OneToMany mapping using a join table, the
foreign key is in a join table.
If the join is for an element collection, the foreign key is in a collection table.

Accessing multiple tables

I am learning EJB,JPA and have very basic doubt
I have 3 Tables A,B,C
A - ID, Name
B - ID, Name
C - ID, A_ID, B_ID
When i create a Entity Class from Database, i get 3 EJB classes with JPA stuff.
Now in my Managed bean i get either A.Name or B.Name and i need to find the matching entries using C.
Normal SQL Query will look like (may not be the best query)
SELECT a.name FROM schema.A a, schema.B b, schema.C c where b.Name='ABC' and c.B_ID=b.ID and a.ID = c.A_ID;
Now where do i do the above query in my classes.
I came across #SecondaryTable but could not understand how exactly its used.
I also saw em.createQuery( SQL query).getResultList().
Now is the above the best way or is there something available in EJB/JPA which is better.
UPDATE 1:
I was trying to execute the query in em.CreateQuery
em.CreateQuery(SELECT a.name FROM A a, B b, C c where b.Name='ABC' and c.B_ID=b.ID and a.ID = c.A_ID).getResultList();
but i get following error in my GlassFish Server (i am using EclipeLink JPA)
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: ERROR: syntax error at or near ")"
Position:
Error Code: 0
Call: SELECT t0.given_name FROM test.a t3, test.a_b t2, test.b t1, test.a t0 WHERE ((((t1.Name = ?) AND (t2.b_id = t1.id.t1.id)) AND (t0.id = )) AND (t3.id = t2.a_id))
bind => [1 parameter bound]
Query: ReportQuery(referenceClass=Consultant sql="SELECT t0.given_name FROM test.a t3, test.a_b t2, test.b t1, test.a t0 WHERE ((((t1.Name = ?) AND (t2.b_id = t1.id.t1.id)) AND (t0.id = )) AND (t3.id = t2.a_id))")
Now why is the SQl statement is messed up in the error log
1.There is an extra entry test.a t0
2.t2.b_id = t1.id.t1.id
3.t0.id=
How does the error log SQL statement gets generated.
As C is a many to many relationship between A and B it shouldn't be an entity. However I don't think JPA likes it that your join table (C) has it's own ID column. If possible remove the ID column from C and make the combination of A_ID, B_ID your primary key.
Then the entity class A could have:
#JoinTable(name = "C",
joinColumns = { #JoinColumn(name = "A_ID", referencedColumnName = "ID") },
inverseJoinColumns = { #JoinColumn(name = "B_ID", referencedColumnName = "ID") })
#ManyToMany
private Collection<B> bCollection;
I think it is clear what all those annotations mean.
Class B would have:
#ManyToMany(mappedBy = "bCollection")
private Collection<A> aCollection;
The mappedBy attribute tells JPA to use the JoinTable defenition of A::bCollection (A is deduced from the type Collection<A> of the field).
Now if you have an instance of A you can easily get all the B's for that A by getting the property. No need for any SQL/JPQL.
Now as for executing queries you should know that you have JPQL and SQL. JPQL is Java Persistence Query Language and is the language of JPA. SQL is the native language of your database. To execute SQL you need to use the createNativeQuery family of functions. The createQuery functions are for JPQL.
You should prefer JPQL over SQL. The most important difference between the two is that JPQL works on your entities and expect all identifiers to correspond to the names used for your classes and properties. For example if the Name column (first letter uppercase) is mapped to a property called name (lower case) then in a JPQL query you should use name (lower case). Same for entity class names and corresponding table names. JPQL also has build in support for the join table of the many to many relation ship the JPQL query for what you want would be
SELECT a
FROM B b JOIN b.aCollection a
WHERE b.name='ABC'
No need to specify all the join conditions JPA knows them from the annotations on your classes.
#SecondaryTable is not relevant here it is used when a single entity is split into more then one table.

Categories