Hibernate mapping - map through table - java

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;
...
}

Related

JPA repository table is not saving new id's but overriding or modifing

I have A.class:
#Entity
#Data
public class A {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
Integer id;
String someStringInAclass;
#OneToMany(cascade = CascadeType.ALL)
List<SomeObject> someObjectsList;
}
I have SomeObject.class:
#Entity
#Data
public class SomeObject {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
Integer id;
String someString;
Integer someInteger;
}
I save A object in database:
SomeRepository.save(A);
And tables look like this:
Then I save A object again with different values in B object:
SomeRepository.save(A);
Then tables look like this:
Table B has extended correctly, but table A_B just overrode the B_id instead putting the new one 4 rows.
Then when I retrieve the object A from database I obtain only last 4 values from B object.
How to make it work with saving new values (adding new rows to A_B) without overriding?
EDIT:
I added A_id as a foreign key to B table. Again - first save is stored properly:
Then after second save with different values for B, it modifies values for A_id column for firstable inserted 4 values (to null). So the table looks like this:
Hibernate handle unidirectional relationships very inefficiently please try to use bidirectional relationship.
Regarding above issue, you don't need third table here, you can avoid creation of third table by adding #JoinColumn as below :
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_a_id")
List<SomeObject> someObjectsList;
this will add column "fk_a_id" in SOME_OBJECT table.
Since the object is already saved and has an assigned id, JPA assumes you mean the same entry in the database. You need to clone the object:
#Entity
#Data
public class A {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
Integer id;
String someStringInAclass;
#OneToMany(cascade = CascadeType.ALL)
List<SomeObject> someObjectsList;
public A(A source) {
this.someStringInAclass = source.someStringInAClass;
this.someObjectsList = new ArrayList<SomeObject>();
for (SomeObject o : source.someObjectsList) {
this.someObjectsList.add(new SomeObject(o));
}
}
}
Use it as follows:
SomeRepository.save(a);
A aClone = new A(a);
SomeRepository.save(aClone);
I added: (nullable = false) parameter for #JoinColumn the problem is solved.
So above the list there is now:
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(nullable = false)
List<SomeObject> someObjectsList;

JPA OneToMany-Mapping without mapping class for Many-side

In a Spring Boot application using JPA 2.1 and Hibernate, there are two (PostgreSQL) tables of interest:
entity external_id
-- --
id serial id serial
... entity_id int
... external_id int
The relation between entity and external_ids is obviously OneToMany, which I want to use in the JPA mapping as well. A simple way to do this is to create #Entity-mappings for each table and use a #OneToMany-relation:
#Entity
public class Entity {
#Id
private Integer id;
#OneToMany(mappedBy= "entityId")
private Set<ExternalId> externalIds;
}
#Entity
public class ExternalId {
#Id
private Integer id;
#ManyToOne
private Integer entityId;
private Integer externalId;
}
But since the table external_ids just holds a list of numbers for each member of entity, I would like to go without an explicit mapping of the table external_id and immediately map the values of external_id.external_id:
#Entity
public class Entity {
#Id
private Integer id;
#OneToMany(???)
private Set<Integer> externalIds;
}
Is this possible with JPA 2.1 and if so how?
You can utilize #ElementCollection for this purpose:
#ElementCollection
#CollectionTable(name = "TableName", joinColumns=#JoinColumn(name = "JoinColumnName"))
private Set<Integer> externalIds;

Hibernate JPA, one to one relationship with a composite key and a constant value

I'm attempting to implement a limited type of object level ACL and its lead me to a place where I'm attempting to create a #OneToOne relationship using a composite key with a constant and dynamic value.
I have an Entity with a database id and a constant value defined in the class.
public class Entity{
private static final int objectType = 1;
#Id
Integer id;
}
I have an access_levels table with a composite key of objectId and objectType.
public class AccessLevel {
#EmbeddedId
private AccessLevelKey accessLevelKey;
#Embeddable
class AccessLevelKey implements Serializable{
private Integer objectType;
private Integer objectId;
....
}
}
Schema of access_levels
CREATE TABLE access_levels(
object_type INTEGER NOT NULL,
object_id INTEGER NOT NULL,
....
CONSTRAINT access_levels_type_id PRIMARY KEY (object_type, object_id)
);
I'm attempting to come up with a one to one relationship that Entity can use to fetch and update its associated AccessLevel
After taking a look a the docs on Non-Standard Joins it seems like I need something like this,
Inside of Entity:
#OneToOne
#JoinColumns({
#JoinColumn(name = "id", referencedColumnName = "object_id"),
#JoinColumn(name = "access_levels.object_type", referencedColumnName = "1"),
})
private AccessLevel accessLevel;
However this throws a hibernate MappingException at app launch
Caused by: org.hibernate.MappingException: Unable to find column with logical name: 1 in access_levels
Thanks!

Map<String, Entity> with composite key in Entity

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 (?, ?, ?)

Joining tables with composite keys in a legacy system in hibernate

I'm currently trying to create a pair of Hibernate annotated classes to load (read only) from a pair of tables in a legacy system. The legacy system uses a consistent (if somewhat dated) approach to keying tables. The tables I'm attempting to map are as follows:
Customer CustomerAddress
-------------------------- ----------------------------
customerNumber:string (pk) customerNumber:string (pk_1)
name:string sequenceNumber:int (pk_2)
street:string
postalCode:string
I've approached this by creating a CustomerAddress class like this:
#Entity
#Table(name="CustomerAddress")
#IdClass(CustomerAddressKey.class)
public class CustomerAddress {
#Id
#AttributeOverrides({
#AttributeOverride(name = "customerNumber", column = #Column(name="customerNumber")),
#AttributeOverride(name = "sequenceNumber", column = #Column(name="sequenceNumber"))
})
private String customerNumber;
private int sequenceNumber;
private String name;
private String postalCode;
...
}
Where the CustomerAddressKey class is a simple Serializable object with the two key fields. The Customer object is then defined as:
#Entity
#Table(name = "Customer")
public class Customer {
private String customerNumber;
private List<CustomerAddress> addresses = new ArrayList<CustomerAddress>();
private String name;
...
}
So, my question is: how do I express the OneToMany relationship on the Customer table?
I may have an answer for my own question. Add the following to Customer:
#OneToMany(mappedBy="customer")
#JoinColumn(name="customerNumber")
List<CustomerAddress> addresses = new ArrayList<CustomerAddress>();
And the following to CustomerAddress:
#ManyToOne
#JoinColumn(name="customerNumber")
protected Customer customer;
This turns out to be a simpler problem to solve than I first thought. Typical.
I assume you have read the Bauer/King Hibernate book, which is extremely bad at explaining how do implement composite primary keys correctly. Don't let yourself be fooled by a bad book: composite primary key are not a property of legacy systems...

Categories

Resources