Map<String, Entity> with composite key in Entity - java

I've got the following two tables:
TABLE A:
INT id
VARCHAR x
PRIMARY KEY (id)
TABLE B:
INT a_id
VARCHAR locale
VARCHAR z
PRIMARY KEY (a_id, locale)
It's basically a simple OneToMany relation. Table B contains the the id (a_id) of the referenced row in Table A plus a locale. This means: Every entry in A can have 0..* entries in Table B, each one with a distinct locale value.
I have the following two classes, which should represent those tables:
#Entity
#Table(name="A")
class A {
#Id
#Column(name="id")
int id;
#Column(name="x")
String x;
#OneToMany(mappedBy="a") // ???
#MapKey... // ???
Map<String, B> bMap;
}
#Entity
#Table(name="B")
class B {
#ManyToOne
#JoinColumn(name="a_id")
A a;
#Column(name="locale")
String locale;
#Column(name="z")
String z;
}
Now two things are missing:
The Annotations for Map<String, B> bMap. I just don't know if I should use a #MapKey or #MapKeyColumn and how to map to that composite key. And if I should/have to use #OneToMany?
The B class of course needs a composite key. Should I use an #EmbeddedId or #IdClass?
Could you provide some example code for this scenario?
Thank you!

Working solution at the bottom
I think, I've now managed to put things together. At least the generated SQL Tables look right, though I still have to figure out how to get Cascaded Saving done...
#Entity
#Table(name="A")
public class A {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
long id;
#Column(name="x")
String x;
#OneToMany(mappedBy="id.a", cascade=CascadeType.ALL, orphanRemoval=true)
#MapKey(name="id.locale")
Map<String, B> bMap = new HashMap<String, B>();
}
#Entity
#Table(name="B")
public class B {
#EmbeddedId
BPK id;
#Column(name="z")
String z;
}
#Embeddable
public class BPK implements Serializable {
#ManyToOne
#JoinColumn(name="a_id")
A a;
#Column(name="locale")
String locale;
// equals + hashcode
}
When calling aRepository.findById(...) Hibernates gives:
Hibernate: select * from A a where a.id=?
which is correct.
But if I call aEntity.getBMap() it always fetches the whole map, even if I just want to use aEntity.getBMap().put("EN", someBObject) and don't want to read any data from it. But that's okay for now.
Now I've just to figure out how to get Cascaded Saving to work. When doing aEntity.getBMap().put("EN", someBObject); aRepository.save(eEntity); I get
org.hibernate.id.IdentifierGenerationException: null id generated for:class B
I think I'm just missing some setters for the #EmbeddedId or it's fields.
FINALLY SOLVED:
Cascaded saving somehow didn't work with #EmbeddedId composite key. So I thought about it and figured out, that I could instead use an #ElementCollection! :).
So here's what I finally did:
#Entity
#Table(name="A")
public class A {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
long id;
#Column(name="x")
String x;
#ElementCollection
#CollectionTable(name="B", joinColumns=#JoinColumn(name="a_id"))
#MapKeyColumn(name="locale")
Map<String, B> bMap = new HashMap<String, B>();
}
#Embeddable
public class B {
#Column(name="z")
String z;
}
Hibernate outputs:
A a = aRepository.findById(...)
Hibernate: select * from A where id=?
a.getBMap().put("EN", someBObject)
Hibernate: select * from B where a_id=?
aRepository.save(a)
Hibernate: insert into B (a_id, locale, z) values (?, ?, ?)

Related

Implicit polymorphism vs Explicit polymorphism in Hibernate

I have read the O/R Mapping of Hibernate and I just can't seem to get past the part on polymorphism.
According to https://docs.jboss.org/hibernate/orm/5.0/manual/en-US/html/ch05.html,
Implicit polymorphisms means that instances of the class will be
returned by a query that names any superclass or implemented interface
or class, and that instances of any subclass of the class will be
returned by a query that names the class itself
whereas
Explicit polymorphisms means that class instances will be returned
only by queries that explicitly name that class. Queries that name the
class will return only instances of subclasses mapped
I just want to understand how these 2 work. Can somebody explain these terms using an example(doesn't have to be too complex) with the use of code? I would appreciate your help
First of all the org.hibernate.annotations.Entity annotation is deprecated now. You should use the #Polymorphism annotation instead.
Now, imagine that you have the following schema:
create table TST_STUDENT
(
st_id int not null,
st_name varchar(200),
primary key (st_id)
);
insert into TST_STUDENT values (1, 'Kostya'), (2, 'Yulia'), (3, 'Borya'), (4, 'Misha');
create table TST_TEACHER
(
tcr_id int not null,
tcr_first_name varchar(200),
tcr_last_name varchar(200),
primary key (tcr_id)
);
insert into TST_TEACHER values (1, 'Mikhail', 'Bulgakov'), (2, 'Leo', 'Tolstoy');
and the following mapping:
public interface Person
{
Long getId();
String getName();
}
#Entity
#Table(name = "TST_STUDENT")
public class Student implements Person
{
#Id
#Column(name = "st_id")
private Long id;
#Column(name = "st_name")
private String name;
public Student()
{
}
// getters / setters
}
and Teacher entity:
import org.hibernate.annotations.Polymorphism;
import org.hibernate.annotations.PolymorphismType;
#Entity
#Table(name = "TST_TEACHER")
// #Polymorphism(type = PolymorphismType.EXPLICIT)
public class Teacher implements Person
{
#Id
#Column(name = "tcr_id")
private Long id;
#Column(name = "tcr_first_name")
private String name;
#Column(name = "tcr_last_name")
private String lastName;
public Teacher()
{
}
// getters / setters
}
Now, if you run the following query:
List<Person> persons = em.createQuery("select p from com.your.entities.Person p", Person.class).getResultList();
you will get all rows from the TST_STUDENT table plus all rows from the TST_TEACHER table.
But, if you uncomment this line:
#Entity
#Table(name = "TST_TEACHER")
#Polymorphism(type = PolymorphismType.EXPLICIT) // now we use explicit polymorphism for the Teacher entity
public class Teacher implements Person
The mentioned above query will return only rows from the TST_STUDENT table.
This is what this annotation mean.
By default, when you query a base class entity, the polymorphic query will fetch all subclasses belonging to the base type. You can even query interfaces or base classes that don’t belong to the JPA entity inheritance model.
P.S. See also this part of documentation.

Map part of composite PK to composite FK

My data model consists of items with a history. I'll call a point in time an "instant"; all tables therefore have an "instant_id" that specifies how that item was configured at that instant. The "instant_id" is rolled into a composite primary key for all tables. Imagine the following example:
Table Computer
============
PK int instant_id <-- Shared id
PK int computer_id <-- Child id
int computer_type_id <-- Parent id
varchar foo
Table ComputerType
==================
PK int instant_id <-- Shared id
PK int computer_type_id <-- Parent id
varchar bar
There is a foreign key in Computer mapping (instant_id, computer_type_id) to the ComputerType primary key.
We use something like
#Embeddable ComputerId {
#Column(name='instant_id', nullable=false) int instant_id,
#Column(name='computer_id', nullable=false) int computer_id
}
Then:
Computer {
#EmbeddedId ComputerId id;
#MapsId('instant_id')
#ManyToOne
#JoinColumns({
#JoinColumn(name='instant_id',...),
#JoinColumn(name='computer_type_id',...)
})
ComputerType computerType;
}
No matter how I combine MapsId with JoinColumns, I can't seem to get this to work. Any ideas?
I don't see a ManyToOne association. You are not showing us how ComputerType is declared, I am assuming it's an Entity. If that's the case, per table definition you provided, both Computer and ComputerType share a composite primary key: instant_id and computer_type_id.
If that is true and they share the same primary key, you might be better off normalizing those two tables into one table.
I think I understand the issue now. You need to consider the computer_type_id as part of the composite key for Computer table as well. The column computer_type_id by itself is not quite meaningful; in the ComputerType table it is a part of the primary key, the other part being instant_id. So if that's the case, you need to include it as part of the primary key for Computer table as well, because you will never have a case where Computer.instant_id = ComputerType.instant_id AND Computer.computer_type_id <> ComputerType.computer_type_id, for a given related association. (If I understand this case correctly)
If you agree with that, then here is the solution:
#Embeddable
public class ComputerId implements Serializable {
int computer_id;
#ManyToOne
#JoinColumns({#JoinColumn(name = "instant_id", insertable=false, updatable=false),
#JoinColumn(name = "computer_type_id", insertable=false, updatable=false) })
ComputerType computerType;
// getters and setters
}
#Entity
public class Computer {
#EmbeddedId
ComputerId computerId;
// getters and setters
}
public class ComputerTypeId implements Serializable {
#Column(name="instant_id", nullable=false) int instant_id;
#Column(name="computer_type_id", nullable=false) int computer_type_id;
// getters and setters
}
#Entity
public class ComputerType {
#EmbeddedId
ComputerTypeId computerTypeId;
String bar;
// getters and setters
}
Finally, you might want to consider Hibernate Envers for Entity versioning.
Hope this helps.

Java/Hibernate/JPA: cannot persist with compound key -> transient object

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.

Hibernate Criteria for Join Table

Help me Hibernate Guru..
i have 2 relationship class, let's call class A and B
#Entity
#Table(name="A")
public class A extends Serializable{
#Id
#Column(name="a_id")
private int id;
#Column(name="a_name")
private String name;
/*
*.....Setter and Getter
*/
}
#Entity
#Table(name="B")
public class B extends Serializable{
#Id
#Column(name="b_id")
private int id;
#ManyToMany(
fetch= FetchType.EAGER,
targetEntity=package.A.class,
cascade={CascadeType.ALL}
)
#JoinTable(
name="B_A",
joinColumns=#JoinColumn(name="b_id"),
inverseJoinColumns=#JoinColumn(name="a_id")
)
#Fetch(FetchMode.SUBSELECT)
private List<A> list;
/*
*.....Setter and Getter
*/
}
Hibernate will generate 3 Table A, B, and B_A. with table B_A have 2 foreign key, one foreign key for primary key table A and one again foreign key for primary key table B,
i want select data from table A, like query :
select * from A a inner join B_A ba on ba.a_id = a.id inner join B b on b.b_id = ba.b_id where b.id in(?, ?, ?, ?)
so how Criteria code i have to create???? and for expected list result List i want to use Transformer.
Thanks
Try this:
criteria.createCriteria(A.class)
.createCriteria("id", "join_between_a_b")
.add(Restrictions.eq("some_field_of_A", someValue));
Remember, A and B have to have id as their identifiers in order to make them join.
By some_field_of_A I mean anything you like in class A, like name for example. You can have restriction over any properties of these classes.

Hibernate mapping - map through table

I have the following database structure:
Table 1 Table 2
Table 3
tid_1 ----(many-to-one)---- tid_1
.... tid_2 ----(one-to-many)---- tid_2
tkey
tvalue
Is there a way to create a class, defined by Table 1, with java.util.Map, associating tkey with tvalue from Table 3? I'm pretty new to Hibernate, and, before asking, I've tried to search and experiment, but got nothing. Any help will be appreciated.
P.S.
If this will not obstruct you, I'd prefer using .hbm.xml style.
You can declare a map with tkey as a key and an entity mapped to Table 3 as a value:
#Entity
public class Table1 {
#ManyToMany
#JoinTable(name = "Table2")
#MapKey(name = "key")
private Map<String, Table3> table3s;
...
}
#Entity
public class Table3 {
#Column(name = "tkey")
private String key;
#Column(name = "tvalue")
private String value;
...
}

Categories

Resources