Persisting a Map where the Key is an Entity with Hibernate and Spring - java

New to Hibernate and I want to persist a hashmap where the key is another entity. I've gone through a number of online articles and similar questions and all of them produce some form of IllegalStateException.
Here is what I have currently:
#Entity
public class SecondClass extends DomainObject {
#Id
#GeneratedValue
private Long id;
#OneToMany ( cascade = CascadeType.ALL, fetch = FetchType.EAGER )
#ElementCollection
#MapKeyClass ( MyEntity.class )
private final Map<MyEntityClass, Integer> myMap;
}
I am too inexperienced at Hibernate to know what the problem is or how to fix it. Any help would be appreciated.

According to the JPA WikiBook, you might need to use #MapKeyJoinColumn to specify the column that's used to join two entities. I also don't think you want both #OneToMany and #ElementCollection for the same relationship.
For example, this should work:
#Entity
public class SecondClass extends DomainObject {
#Id
#GeneratedValue
private Long id;
#ElementCollection
#MapKeyJoinColumn(name = "MY_ENTITY_ID")
private final Map<MyEntityClass, Integer> myMap;
}

Related

How do I cascade persist an #OneToMany relationship with an #EmbeddedId using Spring Data / Hibernate

I've seen a lot of similar questions asked about this but haven't found a solution that fixes the problem I'm seeing, so apologies up front if this is a redundant question. In my situation I have various types of entities and they're each going to have their own tag associations. So I want a generic Tag class that won't have it's own id, but rather an id / composite key made of the id of the entity it's tagging, plus the tag type. To (attempt to) achieve this I made an #Embeddable id class:
#Embeddable
public class TagId implements Serializable {
#Column(columnDefinition = "BINARY(16)")
private UUID parentId;
private String value;
// Getters, setters...
}
That Id is in turn used by a #MappedSuperclass:
#MappedSuperClass
public class Tag {
#EmbeddedId
private TagId id;
// Other attributes, getters, setters...
}
... and then when I want to tag a specific entity, for example using a BookTag, the table would have a book_id column as a foreign key to a Book table taking the place of parentId :
#Entity
#Table(name = "book_tag")
#AttributeOverride(name = "parentId", column = #Column(name = "book_id"))
public class BookTag extends Tag {
// other attributes, getters, setters...
}
Then finally, I have a Book entity:
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue
#Column(columnDefinition = "Binary(16)")
private UUID id;
// other attributes, getters, setters...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.parentId")
private List<BookTag> tags;
}
When I then try to save a new Book, with a populated BookTag collection, using a Spring Data JPA repository to repo.save(book), my desired behavior is that the Book is saved, then the id is copied to the BookTag objects, and those are saved. Unfortunately, what I'm seeing in the log is that Book is inserted as expected, then the inserts for the Tag objects are run, but book_id is being bound as null for each of the entries.
I've tried a few other approaches:
#JoinColumn instead of mappedBy
#MapsId with a #ManyToOne reference to Book on BookTag
#GeneratedValue on parentId
None worked, but it is possible my syntax was off. Thanks in advance for anyone who knows how to tackle this problem.
To anyone who wants to do something similar, I finally found a solution that meets my criteria.
TagId was modified to this:
#Embeddable
public class TagId<T> implements Serializable {
#ManyToOne
private T taggedEntity;
private String value;
// Getters, setters...
}
...which leads to a slight modification to Tag...
#MappedSuperClass
public class Tag<T> {
#EmbeddedId
private TagId id;
// Other attributes, getters, setters...
}
...and then BookTag...
#Entity
#Table(name = "book_tag")
public class BookTag extends Tag<Book> {
// other attributes, getters, setters...
}
...and finally Book:
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue
#Column(columnDefinition = "Binary(16)")
private UUID id;
// other attributes, getters, setters...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.taggedEntity")
private List<BookTag> tags;
}
Now I can add 1...* BookTags to a Book, and in turn I have to set the Book on all the BookTags, but then it's one call to bookRepository.save() and everything cascades down. It would have been nicer to just do it with an id, but a generic is flexible enough. I'll just have it implement an interface so that toString/hashCode/equals can call getId on parent.
The only other drawback is I couldn't get #AttributeOverride to work, so while I'd prefer that my BookTag table have a book_id column, tagged_entity_id will have to suffice.

JPA Entity relationship?

I have entity called Shop and different types of shops (sport, clothes, tech...). That list of types will probably be predefined. One shop can have multiple types. What is the best way to represent that?
I created two entities Shop and Type.
#Entity
#Table(name = "store")
public class Store {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long id;
...
}
#Entity
#Table(name = "type")
public class Type {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long id;
private String name; //sport, clothes, tech...
}
What type of relationship between these two entities should I use?
Given that you said Type is probably predefined, it seems more reasonable to model it as enum, and making use of ElementCollection
(I have to admit that I haven't tried to use them both in combination, I believe it should work though :P )
public enum Type {
SPORT, CLOTHES, TECH
}
public class Shop {
#Id
private Long id;
#ElementCollection
#CollectionTable(
name="SHOP_TYPE",
joinColumns=#JoinColumn(name="SHOP_ID")
)
#Column(name="TYPE")
// mapping for enum by usertype or other way, depending on JPA version you are using
private List<Type> types;
}
Of course, you can model SHOP_TYPE as an entity (e.g. ShopType) if you want more complicated operations on it, but what described above looks to me a more reasonable domain model.
Even you do not want the Type to be predefined (i.e. you can create whatever type in your application), it is still more reasonable to model it as a ManyToMany relationship:
public class Type {
#Id
#Column(name="TYPE_ID")
private Long id
#Column(name="TYPE_NAME")
private String name;
}
public class Shop {
#Id
#Column(name="SHOP_ID")
private Long id;
#ManyToMany
#JoinTable(
name="SHOP_TYPE",
joinColumns=#JoinColumn(name="SHOP_ID"),
inverseJoinColumns=#JoinColumn(name="TYPE_ID"))
private List<Type> types;
}
Just one thing to note: It does not look right to have a Type entity which contains a String as type name, and refer to Shop (as some of the answer suggested). Type should be an entity of itself, and, for example, different shops having CLOTHES type should refer to same CLOTHES entity (unless you view types as some kind of arbitrary tag)
The Store and Type many to many relationship is linked with a third / join table named STORE_TYPE_MAPS.
Store Entity:
#Entity
#Table(name = "store")
public class Store {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY, targetEntity=Type.class)
#JoinTable(name="STORE_TYPE_MAPS",
joinColumns=#JoinColumn(name="STORE_ID"),
inverseJoinColumns=#JoinColumn(name="TYPE_ID")
private Set<Type> types;
//... getter-setter
}
If Type is an Entity then make it ManyToMany
#ManyToMany
#JoinTable(name="Store_Type")
private List<Type> types;
also it can be an enum
I rather prefer to create a new entity called ShopType, the ManyToMany relationship will be created as explained below.
This new Entity allows you to have extra columns in the join table, "ShopType", (which can't be done with a simple #ManyToMany). For example, you can add this information: "the number of articles of each type in each shop".
The code is as follows:
public class Shop {
#Id
#Column(name="SHOP_ID")
private Long id;
#OneToMany(mappedBy = "shop", cascade = CascadeType.ALL)
private List<JoinAchatType> joinShopType = new ArrayList();
}
public class ShopType {
#ManyToOne
#JoinColumn(name = "SHOP_ID")
private Shop shop;
#ManyToOne
#JoinColumn(name = "TYPE_ID")
private Type type;
private int numberArticle;
}
public class Type {
#Id
#Column(name="TYPE_ID")
private Long id;
#OneToMany(mappedBy = "type", cascade = CascadeType.ALL)
private List<JoinAchatType> joinShopType = new ArrayList();
}
For more information check these links:
Mapping many-to-many association table with extra column(s)
.
The best way to use the #ManyToMany annotation with JPA and Hibernate.

JPA mapping to Map<Object, Interger>

I've 3 tables:
- lots: has column gen_id
- onhand_quantities: has column gen_id,sub_inventory_code, quantity
- subinventory: has column sub_inventory_code
I map to 2 objects: Lots, SubInventory and I would like to mapping in object Lots with property is: Map<SubInventory, Integer> getOnhandQuantity()
Integer type in this Map is value of quantity column.
Please help me.
Just a brief search in Hibernate document give me information on using an associated entity as key of a Map (aka Ternary Association)
Quoted from the manual:
There are three possible approaches to mapping a ternary association.
One approach is to use a Map with an association as its index:
Example 7.31. Ternary association mapping
#Entity
public class Company {
#Id
int id;
...
#OneToMany // unidirectional
#MapKeyJoinColumn(name="employee_id")
Map<Employee, Contract> contracts;
}
// or
<map name="contracts">
<key column="employer_id" not-null="true"/>
<map-key-many-to-many column="employee_id" class="Employee"/>
<one-to-many class="Contract"/>
</map>
A second approach is to remodel the association as an entity class.
This is the most common approach. A final alternative is to use
composite elements, which will be discussed later.
To map it back to your original example: Company -> Lots, Employee -> SubInventory, Contract -> OnhandQuantity
Personally I would rather make them as a simple relationship, and construct Map etc on the fly whenever it is needed.
Haven't tried, but a further look get me to an example in #MapKeyColumn, which in combination with the above sample, should give something reasonable like:
class Lot {
#ElementCollection
#MapKeyJoinColumn(name="sub_inventory_id")
#CollectionTable(name="onhand_quantities")
#Column(name="quantity")
private Map<SubInventory, Integer> onhandQuantities;
}
The below code in the Lots entity should do.
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "ONHAND_QUANTITIES", joinColumns= #JoinColumn(name="GEN_ID"))
#MapKeyJoinColumn(name="SUBINVENTORY_CODE")
#Column(name="QUANTITY")
private Map<SubInventory, Integer> onHandQuantity;
I am trying to test this.
I got this solution from the below link.
Reference key issue while doing many to many relationship using #ElementCollection, #MapKeyJoinColumn
#Entity
#Table(name = "LOTS")
public class Lots implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "GEN_ID")
private Long id;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "ONHAND_QUANTITIES", joinColumns= #JoinColumn(name="GEN_ID"))
#MapKeyJoinColumn(name="SUBINVENTORY_CODE")
#Column(name="QUANTITY")
private Map<SubInventory, Integer> onHandQuantity;
......// getters and setters
}
#Entity
#Table(name = "SUBINVENTORY")
public class SubInventory implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name="SUBINVENTORY_CODE")
private Long id;
......// getters and setters
}

hibernate, stackoverflow with particular entity mapping

I have the following mapping:
#Entity
public class Satellite implements Serializable, Comparable<Satellite> {
#NotNull #Id
private long id;
.....
#OrderColumn
#OneToMany(mappedBy = "satellite", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<DataModel> dataModel;
}
and a child entity:
#Entity #IdClass(value=DataModelPK.class)
public class DataModel implements Serializable, Comparable<DataModel> {
private static final long serialVersionUID = -3416403014857250990L;
#Id
private int orbit; // related to reference orbit file
private int dataPerOrbit; // in Gbit
#ManyToOne #Id
private Satellite satellite;
}
originally, DataModel was an embeddable entity, but for a better control over the primary key and the underlying structure of the db, I switched to a more traditional model.
The point is, during the loading of the entity now it generate a stack overflow!! I think there is some cyclic loading between those two entities and it got stuck!
I'm thinking to revert everything back to what it was, but I wish to understand why it gives me this error.
You have #IdClass for DataModel specified to be DataModelPK.class but your #Id annotation is on an int field.
This is a problem, it may be causing you stackoverflow but I am not certain.
Update I now see the second #Id annotation so I stand corrected, I will investigate furtuer.

Map a list of strings with JPA/Hibernate annotations

I want to do something like this:
#Entity public class Bar {
#Id #GeneratedValue long id;
List<String> Foos
}
and have the Foos persist in a table like this:
foo_bars (
bar_id int,
foo varchar(64)
);
UPDATE:
I know how to map other entities, but it's overkill in many cases. It looks like what I'm suggesting isn't possible without creating yet another entity or ending up with everything in some blob column.
Here is how you would do this if you are using JPA2:
#Entity public class Bar {
#Id #GeneratedValue long id;
#ElementCollection
#CollectionTable(name="foo_bars", joinColumns=#JoinColumn(name="bar_id"))
#Column(name="foo")
List<String> Foos;
}
For a clearer example see section 2.2.5.3.3 in the Hibernate Annotations Reference Guide.
This is in Hibernate terms a "collection of values" or "elements". There is a (Hibernate specific) annotation for it. JPA does not support this (yet).
In short, annotate your collection like this:
#CollectionOfElements
#JoinTable(
table=#Table(name="..."),
joinColumns = #JoinColumn(name="...") // References parent
)
#Column(name="...value...", nullable=false)
This will create the necessary table with foreign keys and restrictions.
If you store your list as an array, it works:
setFoos(String[] foos);
you can transform it like this:
setFoos(myList.toArray(new String[myList.size()]));
create an entity 'FooBars'
refactor the attribut 'Foos' to
#OneToMany
List Foos
I'm think it's that what you need:
#Entity
public class Bar {
#Id #GeneratedValue long id;
#OneToMany(mappedBy="bar") //"bar" = field name in mapping class
List<FooBar> Foos;
}
#Entity
public class FooBar {
#Id #GeneratedValue long id;
#ManyToOne
#JoinColumn(name="bar_Id")
Bar bar;
}
Here 'Foos' is List of String, So it is unidirectional.
We can do this in one model class using #ElementCollection annotation.
#Entity
#Table(name="bar")
public class Bar {
#Id #GeneratedValue
long id;
#ElementCollection
#JoinTable(
name="foo_bars",
joinColumns = #JoinColumn( name="bar_id")
)
#Column(name="foo")
List<String> Foos;
}
In DB bar_id is the foreign key in foo_bars table

Categories

Resources