I'm using the Hibernate 3.6 implementation of JPA. I have two tables in a logical parent-child relationship. Normally there would be one record in the "child" for each record in the "parent." The tables look like this:
CREATE TABLE FOO (
FOO_ID INT IDENTITY,
BAR_ID INT DEFAULT 0 NOT NULL,
SOMEDATA CHAR(50) );
CREATE TABLE BAR (
BAR_ID INT IDENTITY,
OTHERDATA CHAR(50),
STILLOTHER CHAR(50)) ;
However, it has been decided that some "parent" records should not have "child" records and I should just put a 0 in the FOO.BAR_ID column.
How do I implement this in my entity classes? Right now the classes look like this:
public class Foo implements java.io.Serializable {
#Id
#GeneratedValue
#Column(name = "FOO_ID", unique = true, nullable = false)
private Long fooId;
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinColumn(name = "BAR_ID", nullable = false)
private Bar bar;
}
public class Bar implements java.io.Serializable {
#Id
#GeneratedValue
#Column(name = "BAR_ID", unique = true, nullable = false)
private Long barId;
)
I can't seem find the right syntax to place a 0 in the FOO.BAR_ID column and not try to create a record in the BAR table. Can this even be done with Hibernate?
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinColumn(name = "BAR_ID", nullable = false)
I believe you should try removing.
nullable = false
You are telling hibernate that Foo's Bar can not be null. If Bar is optional then sometimes it must be null. Since it can not be null and the CascadeType is Persist, Hibernate is saving the Bar that Foo must have when you save Foo.
Related
I'm programming a Multi-Tenant application. The tenant_id is present in every table and is part of each PK.
My tables are:
table S {
tenant_id PK
id PK
pid
...
}
table P {
tenant_id PK
id PK
...
}
Table S has a PK made up with (tenant_id, id). Table P has a PK made up with (tenant_id, id). Table S links to table P via (S.tenant_id = P.tenant_id and S.pid = P.id)
When I've implemented this in Java JPA, I can read table S including P, but adding a S has a strange behavior: JPA adds the missing P but doesn't fill S.pid so the P is correct but there is no link from S to P.
I don't want to have bidirectional links because P is also referred to by other fields of other tables.
My problem is somewhat similar to another question which has no answer yet.
The Java classes look like (simplified):
#Embeddable
public class MultiTenantId implements Serializable {
#Column(name = "tenant_id")
private String tenantId;
#Column(name = "id")
private String id;
...
}
#Entity
#Table(name = "P")
public class P {
#EmbeddedId
private MultiTenantId id;
...
}
#Entity
#Table(name = "S")
public class S {
#EmbeddedId
private MultiTenantId id;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "tenant_id", referencedColumnName = "tenant_id", insertable = false, updatable = false),
#JoinColumn(name = "pid", referencedColumnName = "id", insertable = false, updatable = false)}
)
private P p;
...
}
If I don't set the insertable = false, updatable = false then error
because the tenant_id is already used (in the MultiTenantId and in
this #ManyToOne)
if I set them then reading is OK but adding a S via JPA has a strange effect in the DB: it correctly adds the P, correctly adds the S except that it never sets the pid in the table S ==> the link is not done.
Thanks a lot in advance for your help, I'm completely lost with this problem !!
This is a shortened version of the entities where I only show the relevant parts.
#Entity
#Data
public class Wrapper {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id
#OneToOne(mappedBy = "wrapper", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Application application;
public Wrapper(Application application) {
this.application = application;
application.setWrapper(this);
}
}
#Data
#Entity
#EqualsAndHashCode(exclude = "wrapper")
public class Application {
#Id
private Integer id;
#JsonIgnore
#OneToOne
#JoinColumn(name = "id")
#MapsId
private Wrapper wrapper;
#OneToMany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#SortNatural
private SortedSet<Apartement> ownedApartements = new TreeSet<>();
}
#Entity
#Data
public class Apartement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "application_id", insertable = false, updatable = false)
private Application application;
}
#Repository
public interface WrapperRepository extends JpaRepository<Wrapper, Integer> {
}
The above entities generates the following create table statements:
create table Wrapper (
id int identity not null,
primary key (id)
)
create table Application (
id int not null,
primary key (id)
)
create table Apartement (
id int identity not null,
application_id int not null,
primary key (id)
)
alter table Apartement
add constraint FKsrweh1i1p29mdjfp03or318od
foreign key (application_id)
references Application
alter table Application
add constraint FKgn7j3pircupa2rbqn8yte6kyc
foreign key (id)
references Wrapper
Given the follow entities and the following code:
Apartement apartement1 = new Apartement()
Apartement apartement2 = new Apartement()
Wrapper wrapper = new Wrapper(new Application());
Application application = wrapper.getApplication();
application.getOwnedApartements().addAll(Arrays.asList(apartement1, apartement2));
apartement1.setApplication(application);
apartement2.setApplication(application);
WrapperRepository.saveAndFlush(wrapper);
I see three inserts in the log.
First wrapper, then application, and finally apartement. But for some reason application_id is null on the first save. But I know it has a bi-directional relationship.
The error I get is:
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "APPLICATION_ID"; SQL statement:
insert into Apartement (id) values (null) [23502-197]
Why does this happen? Do I need to store everything in the correct order? Do I need to first store wrapper and application, then finally store the apartement once I have application ID?
Cannot hibernate store all three in one go? Or figure this out it self?
Sorry I fixed it.
The problem was
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "application_id", insertable = false, updatable = false)
private Application application;
I removed insertable = false, updatable = false and added optional=false
That worked
#JoinColumn(name = "application_id", optional = false)
Try this:
Apartement apartement1 = new Apartement()
Apartement apartement2 = new Apartement()
Wrapper wrapper = new Wrapper(new Application());
Application application = wrapper.getApplication();
application.getOwnedApartements().addAll(Arrays.asList(apartement1, apartement2));
apartement1.setApplicationId(application.getId());
apartement2.setApplicationId(application.getId());
WrapperRepository.saveAndFlush(wrapper);
So nothing I tried seems to work. I would like to have something like this:
class A {
B foo;
B bar;
}
class B {
A baz;
}
What I tried in class A is as follows:
#OneToOne(targetEntity = B.class)
#JoinColumn(name = "foo_id")
#Cascade(CascadeType.ALL)
public B getFoo() {
return foo;
}
#OneToOne(targetEntity = B.class)
#JoinColumn(name = "bar_id")
#Cascade(CascadeType.ALL)
public B getBar() {
return bar;
}
which does not seem to work. I always end up where foo_id and bar_id is same for a reason I do not understand.
So when I inspect table "A" in my DB for row with id 1, I would like to have:
foo_id = 1
bar_id = 2
and in Table B, I should have 2 entities with id 1 and 2, where both have baz_id = 1;
Is baz_id intended to be a FK back to A? Because I think the database mapping to model is wrong in that case. You've already established the FK relationship from the PK of B to either A.foo_id or A.bar_id.
Also be careful with your cascading rules on a relationship like this. SQL Server will reject two FKs to the same table unless the DB action for cascading is "no action".
I do happen to know that what you're trying to do is possible in JPA, since I just recently did it on an entity myself:
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#JoinColumn(name = "portal_logo_id", referencedColumnName = "id", nullable = true)
private PortalResourceModel logo;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#JoinColumn(name = "portal_favicon_id", referencedColumnName = "id", nullable = true)
private PortalResourceModel favicon;
I also don't have a mapping in PortalResourceModel for logo or favicon, because that side of the relationship doesn't know how it is being used. And I can't have a generic mapping from multiple relationships on the owning side to a single relationship on the mappedBy side.
We have 2 entities with a #ManyToOne relationship.
When we create an instance of EntityB within a #Transactional method, entityAId (insertable = false updatable = false), is not updated automatically - even though that the entityA instance was already persisted.
Is there a way around this? Do we have to update it manually in the ctor?
#Entity
public class EntityA {
#Id
#GeneratedValue
private Long id;
public EntityA() {
super();
}
...
}
#Entity
public class EntityB {
#Id
#GeneratedValue
private Long id;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
private EntityA entityA;
#Column(name = "entityA_id", insertable = false, updatable = false)
private Long entityAId;
public EntityB() {
super();
}
public EntityB(EntityA entityA) {
super();
this.entityA = EntityA;
}
...
}
EDIT: Also tried the following, but still entityAId = null within the transaction (even though entityA was persisted before).
#Entity
public class EntityB {
#Id
#GeneratedValue
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "entityA_id", insertable = false, updatable = false)
private EntityA entityA;
#Column(name = "entityA_id")
private Long entityAId;
...
}
Hibernate is not going to populate entity fields 'on the fly' (when you change some other fields or similar). It is also not going to do it on persist/flush (exceptions being some special fields like id and version).
Non-insertable/non-updatable fields are populated when entity instances are fetched from the DB. So, to make such fields initialized/refreshed by Hibernate in the same transaction in which you perform changes to the underlying columns they are mapped to, you should first flush the session and then either:
clear the session and re-read the entities;
or, refresh the entities for which you want to reflect such kind of changes.
To update the id field a persist action of the object is required. By default, objects in field entityA are not automatically persisted when persisting an object of EntityB.
I see two possible solutions:
A) Use cascade
#ManyToOne(optional = true, fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST})
private EntityA entityA;
(or use CascadeType.ALL)
B) Persist entityA manually
entityManager.persist(entityA);
To me your mapping does not look right. #ManyToOne or any other association defined between entities but you have defined it on entityAId. Ideally it should be entity (an here you should use insertable = false updatable = false)and you should have separate field entityAId with #column defined on it. Now you should update this field yourself.
If you want to handle hibernate for you remove insertable = false updatable = false
I am using:
Spring 3.2
Hibernate 4.1.9
I need to map, with JPA, three classes. Class A has a ManyToMany relationship with Class B. A unique combination of Class A and Class B need to own a collection of Class C.
Table A
foo
id | name
Table B
bar
id | name
Table C
data
id | xrefId
Join Table -- Unique Key on (fooId,barId)
xref
id | fooId | barId
Altering the existing data structure is not an option.
Edit 1:
Goal: Load a Foo, get its collection of Bars. From each Bar, get its (their!) collection of Data.
Class A
#Entity
public class Foo {
#Id
private UUID id;
#ManyToMany(optional = false)
#JoinTable(name = "xref",
joinColumns = { #JoinColumn(name = "fooId") },
inverseJoinColumns = { #JoinColumn(name = "barId") })
private List<Bar> lstBar = new ArrayList<Bar>();
}
Class B
public class Bar {
#Id
private UUID id;
#ManyToMany(mappedBy = "lstBar")
private List<Foo> lstFoo = new ArrayList<Foo>();
}
Class C
public class Data {
#Id
private UUID id;
}
Just KISS. Make another class Xref, which contains id, foo, bar and Set<Data> fields. Make a DAO method to find an Xref using two parameters foo and bar (implement it with a simple HQL). The unique requirement could be achieved by an unique constraint in the database.
It doesn't look good trying to express it just by the class hierarchy, better to use DAOs.
Your join table, xref, has an extra id field, in order to be able to create such a table with JPA you need an extra entity class XRef and then you have to map the relation between A and XRef and betweem B and XRef (both are one-to-many). Then, you can create the entity class C and map the relation between C and XRef. Do you need more help? I don't have time right now to provide the code, but if you need ask and I will try to add it as soon as possible.
Look at this example (used Integer instead of UUID for simplicity, the rest should be OK).
Bar class:
public class Bar {
#Id
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "barId")
private Collection<Xref> xrefCollection;
}
Foo class:
public class Foo {
#Id
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "fooId")
private Collection<Xref> xrefCollection;
}
Xref class:
public class Xref {
#Id
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "xrefId")
private Collection<Data> dataCollection;
#JoinColumn(name = "bar_id", referencedColumnName = "id")
#ManyToOne(optional = false)
private Bar barId;
#JoinColumn(name = "foo_id", referencedColumnName = "id")
#ManyToOne(optional = false)
private Foo fooId;
}
Data Class:
public class Data {
#Id
private Integer id;
#JoinColumn(name = "xref_id", referencedColumnName = "id")
#ManyToOne(optional = false)
private Xref xrefId;
}
This code has been automatically generated by NetBeans, provided that all tables and indexes are correctly defined in the DB