JPA : reference to a generated JoinTable - java

I have two entities : Product and Aisle.
A product can be in one or many aisles and an aisle can have one or more products.
#Entity
public class Product{
#Id
private Long id;
private String name;
#ManyToMany
#JoinTable(name = "product_aisle",
joinColumns = { #JoinColumn(name = "product_id") },
inverseJoinColumns = { #JoinColumn(name = "aisle_id") })
private Set<Aisle> aisles = new HashSet<>();
/* getters, setters, equals and hashcode */
}
#Entity
public class Aisle{
#Id
private Long id;
private String row;
private String shelf;
#ManyToMany(mappedBy="aisles")
private Set<Product> products = new HashSet<>();
/* getters, setters, equals and hashcode */
}
And I have a last entity : Salesman.
A salesman is responsible for a product in an aisle:
#Entity
public class Salesman{
#Id
private Long id;
private String name;
/* ManyToOne to ProductAisle ?*/
}
Question : How can I reference a Salesman to the auto-created join table (ProductAisle) with a "#ManyToOne" annotation ?
Regards

To represent a Product that is in a specific Aisle you need another entity. This is an example:
#Entity
public class Product{
#Id
private Long id;
private String name;
#OneToMany(mappedBy = "product")
private Set<ProductAisle> productAisle = new HashSet<>;
/* getters, setters, equals and hashcode */
}
#Entity
public class Aisle{
#Id
private Long id;
private String row;
private String shelf;
#OneToMany(mappedBy = "aisle")
private Set<ProductAisle> productAisle = new HashSet<>();
/* getters, setters, equals and hashcode */
}
#Entity
public class ProductAisle{
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
private Product product;
#ManyToOne(fetch = FetchType.LAZY)
private Aisle aisle;
/* getters, setters, equals and hashcode */
}
And then your Salesman would point to a collection of ProductAisle instances, which maps a product with an aisle:
#Entity
public class Salesman{
#Id
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Set<ProductAisle> productAisle;
}

Since both Aisle and Product have bi-directional mappings to each other, you can join any of them (or even both of them) to Salesman class, and you do not need to join service table at all.

Related

Hibernate #ManyToMany with extra columns

So I've been trying the solutions out there to map a ManyToMany relationship with extra columns but none of them is working for me and I don't know what am I doing wrong.
The Many to Many relationship is between Patient and Disease (a Patient can have multiple diseases and a Disease can be suffered by many Patients). The time attribute means "the type of the disease" (acute, chronic...)
My classes are:
#Entity
#Table(name="patient")
public class Patient{
#Id
#NotNull
#Column(name="nss")
private String NSS;
//Some attributes
#OneToMany(mappedBy = "patient")
private Set<PatientDisease> diseases = new HashSet<PatientDisease>();
//Empty constructor and constructor using fields omitted
//Getters and setters ommited
}
,
#Entity
#Table(name="disease")
public class Disease{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private Integer id;
#OneToMany(mappedBy = "disease")
private Set<PatientDisease> patients = new HashSet<PatientDisease>();
//Constructors and getters and setters ommited for brevity
}
Associated class
#Entity
#Table(name = "Patient_Disease")
#IdClass(PatientDiseaseID.class)
public class PatientDisease{
#Id
#ManyToOne(fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
#JoinColumn(name = "nssPatient", referencedColumnName = "id")
private Patient patient;
#Id
#ManyToOne(fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
#JoinColumn(name = "diseaseID", referencedColumnName = "id")
private Disease disease;
#Column(name="time")
private String time;
//GETTERS AND SETTERS OMMITED FOR BREVETY. Constructor NOT Needed following the example
}
The id class:
#Embeddable
public class PatientDiseaseId implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "nssPatient")
private String patient;
#Column(name = "diseaseID")
private Integer disease;
//getters and setters
//hashCode and equals
}
My main app:
...
List<Diseases> diseases = sesion.createQuery("from Disease").getResultList();
System.out.println("Diseases: ");
for(Disease d: diseases) {
System.out.println(d.getName());
for(PatientDisease pd: e.getPatientDisease()) {
System.out.println(pd.getPatient().toString());
}
}
...
When running the main App I get the exception on line 5 (2nd for loop):
Exception in thread "main" org.hibernate.PropertyAccessException: Could not set field value [1] value by reflection : [class entities.PatientDisease.diseases] setter of entities.PatientDisease.diseases
I have tried some solutions here in Stack Overflow an some others that I found on the Internet, but I can't get them to work and I don't know why
Because you are using #IdClass you don't need to annotate PatientDiseaseId with #Embedded and #Column. And you have to refer to the entities.
This is what it should look like:
public class PatientDiseaseId implements Serializable {
private static final long serialVersionUID = 1L;
private Patient patient;
private Disease disease;
//getters and setters
//hashCode and equals
}

Hibernate n+1 issue with 3 entities

I have 3 entities. Category, Subcategory and Product
#Entity
#JsonInclude(value = JsonInclude.Include.NON_EMPTY)
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String title;
private String picture;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
#JsonIgnore
private List<Subcategory> subcategories;
//getters and setters...
#JsonProperty("productCount")
private int getProductCount() {
//Counting products
int productCount = 0;
//!!!!!
//My problem starts here!
for (final Subcategory subcategory : subcategories) {
productCount += subcategory.getProducts().size();
}
return productCount;
}
}
#Entity
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Subcategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String title;
#ManyToOne
#JoinColumn(name = "parent_category_id", nullable = false)
#JsonIgnore
private Category category;
#OneToMany(mappedBy = "subcategory", cascade = CascadeType.ALL)
private List<Product> products;
//getters and setters
}
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String title;
private String unit;
private String icon;
#ManyToOne
#JoinColumn(name = "subcategory_id", nullable = false)
#JsonIgnore
private Subcategory subcategory;
//getters and setters
}
They are related to each other, but when I want to count the number of products in each category, about 120 queries are executed (depending on the number of subcategories and products)
I was able to reduce it to around 60 queries by adding #EntityGraph to my category repository:
#EntityGraph(type = EntityGraph.EntityGraphType.FETCH, attributePaths = {"subcategories"})
However 60 query is still too much. I can't add subcategories.products to this entity graph annotation because that causes org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
I can suppress this exception by changing the data type of products to Set from List but that creates a Cartesian product and makes the performance worse (it returns around 18,000 rows).
How can I fix this issue without creating a Cartesian product?

Spring data jdbc - Specific join, specific mapping

I've got following two tables:
Customer
id
name
Order
id
product_name
customer_id
with a 1 to 1 relation
and java entities:
#Data
public class Customer{
#Id
private Long id;
private String name;
}
#Data
public class Order{
#Id
private Long id;
#Column("id")
private Customer customer; //i want to somehow map this
private String productName;
}
and a controller
#Controller
public class MyController{
//...
#GetMapping("/")
public String getmap(Model m){
System.out.println(repository.findAll()) //prints "nullrows" due to wrong sql statement
return "mytemplate";
}
}
my current issue is, that spring is executing following sql statement:
SELECT Order.id, Order.product_name, Customer.id, Customer.name
FROM Order LEFT OUTER JOIN Customer ON Customer.id = Order.id
what i actually want is to join on Customer.id = Order.customer_id while leaving the classes as they are i.e. the customer reference needs to stay in order.
i've tried every annotation that i could find so far and have made no progress.
EDIT:
I am not allowed to use jpa/hibernate
One workaround is to do the following:
#Data
public class Customer{
#Id
private Long id;
private String name;
}
#Data
public class Order{
#Id
private Long customerId;
private Long id;
#Column("id")
private Customer customer; //i want to somehow map this
private String productName;
}
causing this to automatically join on Customer.id = Order.customer_id
This does not look like a good fix however.
You can use #OneToOne and #JoinColumn annotations for your One-to-One relationship:
#Data
public class Customer{
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "order_id", referencedColumnName = "id")
private Order order;
}
#Data
public class Order{
#Id
#Column(name = "id")
private Long id;
#Column(name = "product_name")
private String productName;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "customer_id", referencedColumnName = "id")
private Customer customer;
}

One-to-One association with composite ID of owner Entity

I have a to make a one-to-one association between two Entities, but one of them must have two #Id. One is PRI another one is MUL. How must i declare composite id, and how do i need to map the classes?
#Entity
#Table(name = "PERSONS")
public class Person implements Serializable{
private static final long serialVersionUID = -3451407520028311143L;
#Id
#Column(name = "ID")
private Integer id;
#Column(name = "ADDRESS_ID")
private Integer addressId;
#Column(name ="NAME")
private String name;
#OneToOne(mappedBy= "person", cascade= CascadeType.ALL)
private Address address;
...
}
second class is mapped via #IdClass annotation
#Entity
#Table ( name = "ADDRESS" )
#IdClass(AddressKeys.class)
public class Address implements Serializable {
#Id
#Column ( name = "ID")
private Integer id;
#Id
#Column ( name = "PERSON_ID")
private Integer idPerson;
#Column ( name = "CITY" )
private String city;
#OneToOne(cascade= CascadeType.ALL)
#JoinColumn(name="PERSON_ID")
private Person person;
...
}
and the id class
class AddressKeys implements Serializable{
private Integer id;
private Integer idPerson;
//getters and setters
#Override
public int hashCode() {
...
return result;
}
#Override
public boolean equals(Object obj) {
...
}
}
So when i try to create and save a record i have a next error
Could not open sessionRepeated column in mapping for entity:
hibernateMappedModels.base1.mappedClasses.oneToOne.Address column:
PERSON_ID (should be mapped with insert="false" update="false")
java.lang.NullPointerException at
hibernateMappedModels.base1.Main.run(Main.java:45) at
hibernateMappedModels.base1.Main.main(Main.java:24
I tryed to make an Id fields unInsertable and unUpdatable, and it was working, but i need them to be insertable and updatable; Is there any possibility to do it?
I am confused by your mappings and not sure what is required other then the simple mappings below: if I am missing something then you will need to expand on your question. You are getting the error as you have mapped the column twice - once via the one-to-one and once as a simple property. Additionally, I am not sure why you require a composite key on address.
#Entity
#Table(name = "PERSONS")
public class Person implements Serializable{
private static final long serialVersionUID = -3451407520028311143L;
#Id
#Column(name = "ID")
private Integer id;
#Column(name ="NAME")
private String name;
#OneToOne(mappedBy= "person", cascade= CascadeType.ALL)
private Address address;
}
#Entity
#Table ( name = "ADDRESS" )
public class Address implements Serializable {
#Id
#Column ( name = "ID")
private Integer id;
#Column ( name = "CITY" )
private String city;
#OneToOne(cascade= CascadeType.ALL)
#JoinColumn(name="PERSON_ID")
private Person person;
}

Hibernate many to many mapping, join table with PK and extra columns

I've done the necessary changes to my models outlined here. However, I don't know what to put on my join table entity.
Note that my join table has a surrogate key , and two extra columns (date and varchar).
What I've got so far is:
User.java
#Entity
#Table (name = "tbl_bo_gui_user")
#DynamicInsert
#DynamicUpdate
public class User implements Serializable {
private String id;
private String ntName;
private String email;
private Set<GroupUser> groupUsers = new HashSet<GroupUser>(0);
// Constructors and some getters setters omitted
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user", cascade=CascadeType.ALL)
public Set<GroupUser> getGroupUsers() {
return groupUsers;
}
public void setGroupUsers(Set<GroupUser> groupUsers) {
this.groupUsers = groupUsers;
}
}
Group.java
#Entity
#Table (name = "tbl_bo_gui_group")
#DynamicInsert
#DynamicUpdate
public class Group implements Serializable {
private String id;
private String groupName;
private String groupDesc;
private Set<GroupUser> groupUsers = new HashSet<GroupUser>(0);
// Constructors and some getters setters omitted
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.group", cascade=CascadeType.ALL)
public Set<GroupUser> getGroupUsers() {
return groupUsers;
}
public void setGroupUsers(Set<GroupUser> groupUsers) {
this.groupUsers = groupUsers;
}
}
The problem is that I don't know what to put on my join table entity. Here it is.
GroupUser.java
#Entity
#Table (name = "tbl_bo_gui_group_user")
#DynamicInsert
#DynamicUpdate
#AssociationOverrides({
#AssociationOverride(name = "pk.user",
joinColumns = #JoinColumn(name = "id")),
#AssociationOverride(name = "pk.group",
joinColumns = #JoinColumn(name = "id")) })
public class GroupUser implements Serializable {
private String id;
private User userId;
private Group groupId;
private Date dateCreated;
private String createdBy;
// constructors and getters and setters for each property
// What now? ? No idea
}
user to group would be a Many-To-Many relation. Now, you are splitting that up into Two One-To-Many Relations. Therefore your Mapping Entity simple needs to complete the Many-To-Many relation, by using Many-To-One:
public class GroupUser implements Serializable {
private String id;
#ManyToOne
private User userId;
#ManyToOne
private Group groupId;
private Date dateCreated;
private String createdBy;
}
See also this example: Mapping many-to-many association table with extra column(s) (The Answer with 38 upvotes)

Categories