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
}
Related
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;
}
Suppose I have two database tables, Product and ProductDetails.
create table Product
{
product_id int not null,
product_name varchar(100) not null,
PRIMARY KEY (product_id)
}
create table ProductDetails
{
detail_id int not null,
product_id int not null,
description varchar(100) not null,
PRIMARY KEY (detail_id,product_id),
FOREIGN KEY (product_id) REFERENCES Product(product_id)
}
Each product can have multiple product detail entries, but each product detail can only belong to one product. In SQL, I want to be able to retrieve each product detail but with the product name as well, and I would do that with a join statement.
select p.product_id,pd.detail_id,p.product_name,pd.description
from Product p join ProductDetails pd on p.product_id=pd.product_id
Now I need to have that concept in Spring data JPA form. My current understanding is the following:
#Table(name = "Product")
public class ProductClass
{
private int productID;
private String productName;
}
#Table(name = "ProductDetails")
public class ProductDetailsClass
{
private int detailID;
private int productID;
// this is the part I don't know how to set. #OneToMany? #ManyToOne? #JoinTable? #JoinColumn?
private String productName;
private String description;
}
(I didn't include any attributes such as #Id to keep the code minimal)
What do I need to write to get this private String productName; working?
My research on the #JoinTable and #OneToMany and other attributes just confuses me more.
P.S. This is a legacy Java program I inherited. The private String productName; part wasn't in the original code, but now I need the ProductDetails class to have the productName available.
P.P.S. I want to have a clear understanding of what I'm doing before trying anything and deploying. This is a legacy program deployed to production, and from what I understand, any code changes here can change the database structure as well, and no amount of money is enough to make me want to restore the Java program, the Spring Framework, the Apache server and MySQL database to a working order if anything catastrophic happens. Also I don't really have a development environment to test this. Help...
You research already goes in the right direction: You would need a #OneToMany relationship. The best descriptions for Hibernate has Vlad Mihalcea. On his webpage you could also find a good explanation of those relationships: The best way to map a #OneToMany relationship with JPA and Hibernate.
Firstly, you would have to create the entities correctly (an entity is represented by a table in a relational database).
Unidirectional (#OneToMany)
#Entity
#Table(name = "product")
public class Product
{
#Id
#GeneratedValue
private Long productID;
private String productName;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProductDetail> productDetails;
//Constructors, getters and setters...
}
#Entity
#Table(name = "product_details")
public class ProductDetail
{
#Id
#GeneratedValue
private Long detailID;
private String description;
//Constructors, getters and setters...
}
This is based on a unidirectional relationship. Therefore, each Product knows all the allocated ProductDetails. But the ProductDetails do not have a link to its Products. However, this unidirectional implementation is not recommended. It results in an increase of the size of the database, even its optimisation with #JoinColumn is not ideal because of more SQL calls.
Unidirectional (#ManyToOne)
#Entity
#Table(name = "product")
public class Product
{
#Id
#GeneratedValue
private Long productID;
private String productName;
//Constructors, getters and setters...
}
#Entity
#Table(name = "product_details")
public class ProductDetail
{
#Id
#GeneratedValue
private Long detailID;
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = product_id)
private Product product;
//Constructors, getters and setters...
}
In this unidirectional relationship only the ProductDetails know which Product is assigned to them. Consider this for a huge number of ProductDetail objects for each Product.
The #JoinColumn annotation specifies the name of the column of the table product_details in which the foreign key to the Product (its id) is saved. It also works without but it is more efficient with this annotation.
Bidirectional (#OneToMany and #ManyToOne)
#Entity
#Table(name = "product")
public class Product
{
#Id
#GeneratedValue
private Long productID;
private String productName;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProductDetail> productDetails;
//Constructors, add, remove method, getters and setters...
}
#Entity
#Table(name = "product_details")
public class ProductDetail
{
#Id
#GeneratedValue
private Long detailID;
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = product_id)
private Product product;
//Constructors, getters and setters...
}
With a bidirectional relationship objects of both sides (Product and ProductDetail) know which other objects got assigned to them.
But according to Vlad Mihalcea, this should not be used if too many ProductDetails exist per Product.
Also remember to implement proper add and remove methods for list entries (see article again, otherwise weird exceptions).
Miscellaneous
With the cascading, changes in a Product also get applied to its ProductDetails. OrphanRemoval avoids having ProductDetails without a Product.
Product product = new Product("Interesting Product");
product.getProductDetails().add(
new ProductDetails("Funny description")
);
product.getProductDetails().add(
new ProductDetails("Different description")
);
entityManager.persist(product);
Often the question about the correct equals and hashCode methods is a complex puzzle in your head. Especially for bidirectional relationships but also in other situations relying on a database connection it is recommendable to implement them quite simply as described by Vlad.
It is good practice to use objects for primitive data types as well. This gives you the option to retrieve a proper null when calling the getter.
Avoiding eager fetching should be quite clear...
When you now try to retrieve a Product out of the database, the object automatically has a list of all the ProductDetails assigned to it. To achieve this, JPA repositories in Spring could be used. Simple methods do not have to be implemented. When you have the need to customise the functionality more, have a look at this article by Baeldung.
I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.
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.
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.