How to create relationships in entities correctly - java

The entity describing the machine
#Entity
#Table(name = "machine")
public class Machine {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "machine_node", joinColumns = #JoinColumn(name = "machine_id"), inverseJoinColumns = #JoinColumn(name = "node_id"))
private List<NodeMachine> nodeMachines = new ArrayList<>();
}
The entity describing the part/node
#Entity
#Table(name = "node_machine")
public class NodeMachine {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Several parts are installed on each machine (nodeMachines list), and each part can be installed on different machines, so ManyToMany was added.
For example, a wheel can be mounted on a motorcycle or a car. A motorcycle can have two wheels, and a car can have four.
I will describe in more detail what is in the tables. I must say right away that the example is not very successful, just for understanding. In the Machine table we have 100 M motorcycles (1-100) and 100 C cars (1-100). And there is only one entry in the NodeMachine table - the K1 wheel, which is suitable for all one hundred motorcycles and for all one hundred cars. From this, there is no way to determine how many wheels each motorcycle and each car should have. Therefore, I believe that there should be a third table where the number of wheels is indicated for each car and motorcycles. And I think it's too redundant to keep 200 records of wheels for motorcycles and 400 records for cars in the table.
Each part is installed on a specific machine in a certain number.
I want to get the number of nodes installed in a particular machine by knowing the node and machine name.
To do this, you will have to create another count_machine_node table with the fields
- machine_id
- node_id
- count
I understand that you will have to create a new entity.
#Entity
#Table(name = "node_machine")
public class CountNodeMachine {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long machine_id;
private Long node_id;
private Integer count;
}
But what connections do you need to register in these entities?
How to link these three tables correctly?
And do I need to create a CountNodeMachine entity?

Please comment if this serves the purpose.
Machine entity:
#Entity
#Table(name = "machine")
public class Machine {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy="machineId")
List<MachinePartCount> count;
Part entity
#Entity
#Table(name = "part")
public class Part {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy="partId")
List<MachinePartCount> count;
Relation entity
#Entity
#Table(name="machine_part")
#IdClass(MachinePartCountPK.class)
public class MachinePartCount {
#Id
private Integer machineId;
#Id
private Integer partId;
private Integer count;
Composite Key
#Embeddable
public class MachinePartCountPK implements Serializable{
private static final long serialVersionUID = 1L;
private Integer machineId;
private Integer partId;
output:
**machineService.findAll()**
getAllMachines: [Machine [id=1, name=m1, count=[MachinePartCount [machineId=1, partId=1, count=2]]], Machine [id=2, name=m2, count=[MachinePartCount [machineId=2, partId=102, count=4]]]]
**partService.findAll()**
getAllParts: [Part [id=1, name=p100, count=[MachinePartCount [machineId=1, partId=1, count=2]]], Part [id=102, name=p10, count=[MachinePartCount [machineId=2, partId=102, count=4]]]]
**machinePartCountService.findAll()**
getmachinePartEntries: [MachinePartCount [machineId=1, partId=1, count=2], MachinePartCount [machineId=2, partId=102, count=4]]
Data in tables:
machine table:
machine
part table:
part
machine_part table:
machine_part

I always suggest to start development from the Java side. So, it might be something like
#Entity
#Table(name = "machine")
public class Machine {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Список узлов машины
#ElementCollection
private Map<NodeMachine, Integer> nodeMachines = new HashMap<>();
}
Also, check my Hibernate articles (Russian and English versions)

You don't need the third entity, as you can do the sql query, or select machine by name, filter it's nodeMachines list and count the result.
For example, using the repository, like here you can select machine by id, or a list of machines by name, and then just filter it's(their's) node lists by name and count.
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface MachineRepository extends CrudRepository<Machine, Long> {
List<Machine> findByName(String lastName);
}
And somewhere in your code:
#Autowired
private MachineRepository machineRepository;
public int countNodesInMachine(String machineName, String nodeName) {
return machineRepository.findByName(machineName).stream()
.flatMap(machine -> machine.getNodeMachines().stream())
.filter(node -> node.getName().equals(nodeName))
.count();
}

To do this, you can create a counter in the NodeMachine entity. This is economical in terms of occupied space in the database, but not true in terms of object orientation. Using the CountNodeMachine entity is not economical at all because a table is wasted to hold a number. I think you need a wrapper class that has NodeMachine and its counts. and use List of this to Machine Entity.

If I get from the above example,
You can do is create a third entity namely as Categories.
As you have a machine specific category, like Motorcycles or Car.
They have a specific set of wheels.
You can map that in this third table and create relation with the Machine.
`Categores
id| category| wheels| othernodes
1 | Car | 4 | 1
2 | Bike | 2 | 3`
and so on, you just will have to find the category and node from the machine table and get the count easily, also you can add new nodes for the same.
The only downside is that you machine will be specific to Category.

Related

Saving entity with one-to-many relationship

I think I have a bad setup for my hibernate database. I have Citizen entities who have one to many relationships with WeeklyCare entities. Below is the relevant code.
Citizen:
#Entity
#Table(name = "citizens")
public class Citizen {
#Id
#Size(max = 10, min = 10, message = "CPR must be exactly 10 characters")
private String cpr;
#OneToMany()
#JoinColumn(name = "cpr")
private List<WeeklyCare> weeklyCare;
}
WeeklyCare:
#Entity
public class WeeklyCare {
#EmbeddedId
private WeeklyCareIdentifier weeklyCareIdentifier;
}
WeeklyCareIdentifier:
#Embeddable
public class WeeklyCareIdentifier implements Serializable {
#NotNull
#Size(max = 10, min = 10, message = "CPR must tbe exactly 10 characters")
private String cpr;
#NotNull
private Integer week;
#NotNull
private Integer year;
}
I have some problems when I want to save data to the database:
I can't save WeeklyCare first, because it requires a Citizen.
When I send the citizens to my backend, the objects contain a list of WeeklyCare. When I try to save the citizens, it gives me this error: Unable to find Application.Models.WeeklyCare with id Application.Models.WeeklyCareIdentifier#b23ef67b
I can solve the problem by clearing the list of WeeklyCare on the Citizen before saving it, and then saving the list of WeeklyCare after, but that feels like a terrible way to do it.
I guess I want hibernate to ignore the list of WeeklyCare when it saves a Citizen, but acknowledge it when it fetches a Citizen. Is this possible? Or is there an even better way to do it? Thanks.
I can't save WeeklyCare first, because it requires a Citizen.
You have the "cpr" identifier used in two entities:
it's the primary Id for Citizen
it's part of the composite Id for WeeklyCare
You could, theoretically speaking, create a list of WeeklyCare (not with the way it is modeled now though) and later update the associations of each WeeklyCare to Citizen.
When I send the citizens to my backend, the objects contain a list of WeeklyCare. When I try to save the citizens, it gives me this
error: Unable to find Application.Models.WeeklyCare with id
Application.Models.WeeklyCareIdentifier#b23ef67b
The best way to map One-To-Many association is bidirectional. This will also save you from some unnecessary queries Hibernate is generating when using #OneToMany with #JoinColumn only.
1) Remove cpr from WeeklyCareIdentifier class (and probably rename the class).
#Embeddable
public class WeeklyCareIdentifier implements Serializable {
#NotNull
private Integer week;
#NotNull
private Integer year;
//constructors, getters, setters
}
2) Remove the composite #EmbeddedId in favor of Long id field:
#Entity
public class WeeklyCare {
#Id
#GeneratedValue
private Long id;
#Embedded
private WeeklyCareIdentifier weeklyCareIdentifier;
//constructors, getters, setters
}
3) Move to bidirectional mapping:
#Entity
#Table(name = "citizens")
public class Citizen {
#Id
#Size(max = 10, min = 10, message = "CPR must be exactly 10 characters")
private String cpr;
#OneToMany(
mappedBy = "citizen",
cascade = CascadeType.ALL, //cascade all operations to children
orphanRemoval = true //remove orphaned WeeklyCare if they don't have associated Citizen
)
private List<WeeklyCare> weeklyCares = new ArrayList<>(); //init collections to avoid nulls
//constructors, getters, setters
//add utility methods to manipulate the relationship
public void addWeeklyCare(WeeklyCare weeklyCare) {
weeklyCares.add(weeklyCare);
weeklyCare.setCitizen(this);
}
public void removeWeeklyCare(WeeklyCare weeklyCare) {
weeklyCares.remove(weeklyCare);
weeklyCare.setCitizen(null);
}
}
and:
#Entity
public class WeeklyCare {
#Id
#GeneratedValue
private Long id;
//having reference to the citizen entity from WeeklyCare
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "citizen_cpr")
private Citizen citizen;
#Embedded
private WeeklyCareIdentifier weeklyCareIdentifier;
//constructors, getters, setters
}
I would also recommend to use Long ids for the entities, even if the cpr is unique and so on. Convert the cpr to a normal column and introduce a DB generated ID column which you use in to join with in your internal domain and treat the cpr as a pure user-facing data column.

Switching foreign key in hibernate is not working

TLDR;
I'm using spring boot and jpa.
I want to switch the foreign key of an object, in this case just switching the category of a vehicle.
But when i try to do that hibernate interprets it as if i'm trying to change the primary key of the category object instead of just switching the foreign key and I get this error
org.hibernate.HibernateException:identifier of an instance of abc.package.mode.Category was altered from 1 to 2
I have an entity Category which i'm using only for categorizing vehicle entity object.
#Entity
public class Category {
#Id
private Long id;
private String name;
}
Here is the Vehicle class which needs to be categorized.
#Entity
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="dish_seq")
private Long id;
private String name;
private Integer price;
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.DETACH)
private Category category;
}
Lets say there's 3 categories,
'Sedan'
'Convertible'
'Hatchback'
If i have a car object,
Nissan-PT76, $30000, category: [id:1, name:Sedan]
When i try to change category manually to [id:2, name:Convertible] and persist it, i get
org.hibernate.HibernateException:identifier of an instance of abc.package.mode.Category was altered from 1 to 2
I cannot switch from one existing object to another. I have tried to look this up in the internet but i couldn't find the right keywords to search for this kind of relationship in hibernate, or does it not allow this kind of relationship at all?
Add column reference to your Category field in the Vehicle class
#JoinColumn(name = "category_id", nullable = false)

JPA ManyToMany join table query

Assuming theses Entities
#Entity
public class EntityNote implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="SeqEntityNote", sequenceName="SeqEntityNote", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SeqEntityNote")
private long id;
private Date date;
private String subject;
private String content;
#ManyToMany
private List<EntityTopic> listEntityTopic;
//setters/getters
#Entity
public class EntityTopic implements Serializable {
#Id
#SequenceGenerator(name="SeqEntityTopic", sequenceName="SeqEntityTopic", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SeqEntityTopic")
private long id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
In my DB, a join table named "entity_note_list_entity_topic" records the ManyToMany relation.
This works correctly so far.
But I'd like to perform a count query like 'how many EntityNotes per EntitityTopic'
Unfortunatly I'm quite lost in this situation.
How this query can be written ?
Do I need other elements in my two entities ?
(In many examples I see a reverse relation using mappedBy attribute on ManyToMany.. Do I need this ?)
It will be the easiest if you make the many to many relation bidirectional. There are no serious extra costs involved, as it uses the same db structure, and the list are lazy loaded so if the relation is not being used the lists are not populated (you can hide the second direction by making accessors private).
Simply change:
#Entity
public class EntityTopic implements Serializable {
...
#ManyToMany(mappedBy="listEntityTopic")
private List<EntityNote> notes;
}
You can issue normal count jpql queries, for example:
SELECT count(n) from EntityTopic t INNER JOIN t.notes n where t.name =:name
so you don't neet to retrieve the notes and topics if don't need to.
But I also believe that your original mapping can also be queries with:
SELECT COUNT(n) FROM EntityNote n INNER JOIN n.listEntityTopic t WHERE t.name = :name
If you have the following code:
#Entity
public class EntityNote implements Serializable {
#ManyToMany(fetch = FetchType.LAZY)
private List<EntityTopic> topics;
}
#Entity
public class EntityTopic implements Serializable {
#ManyToMany(fetch = FetchType.LAZY)
private List<EntityNote> notes;
}
Then, topic.getNotes().size() will give you the number of notes associated with a topic. When using Hibernate as the JPA provider, a SELECT COUNT(...) query is issued for this instead of loading all the associated notes. If this does not work for you out-of-the-box, mark the collections as extra lazy using the instructions in this post.

JPA OneToMany insert not setting the id's correctly

I have two entities, let's say
Person.java:
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private long id;
#OneToMany(mappedBy = "personData", cascade = CascadeType.PERSIST)
private List<SkillsData> skillsData;
// ...
}
SkillsData.java
#Entity
#Table(name = "SkillsData")
public class SkillsData implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private long id;
#JoinColumn(name = "PERSONID")
#ManyToOne(cascade = REMOVE)
private Person personData;
// ...
}
When I create a person, add a list of type SkillsData to it's skillsData field and persist it everything works with no exceptions thrown, but when I browse the database directly in the SkillsData table the field PERSONID is not populated and because of that the skills added can't be referenced to the right person.
I'm trying to fix this problem for quite some time and I'll be thankful for any help.
The problem might be in the fact that you're not setting SkillsData.personData before persisting leaving it null.
You must set it cause adding SkillsData to the Person.skillsData list is not enough since you declared this side of relationship as inverse(mappedBy attribute).
Therefore it is the SkillsData.personData non-inverse side who is responsible for establishing this relationship.

Using JPA mapped by not primary key field?

I am having troubles getting this working and I wonder if what I am doing simply does not make sense?
public class Application {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private long id;
....
}
#MappedSuperclass
public abstract class Sample {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToOne (cascade=CascadeType.ALL)
protected Application application;
....
}
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="application", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
Application application;
}
The problem I am having is that I am able to add parts, the database looks correct, then when I attempt to fetch the the parts list I get an empty list.
I can get it to work if I compromise on the database structure by changing these classes:
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="testSample", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
TestSample testSample;
}
The tables are being auto generated by hibernate, so they are coming out like this:
application
id : number
....
test_sample
id : number
application_id : number
...
part
id : number
application_id : number
If I change it to the less desirable way that works, the last table is different:
part
id : number
test_sample_id : number
Because the id's in all cases are being auto generated, there are no shared primary keys. Essentially what I am trying to do is use mappedby where mappedby is referring to a field that is not the primary key of the table/class called "TestSample". This is what I am not sure if makes sense in JPA.
The OneToMany is bi-directional with the "Part" class. I think this is getting very difficult to explain (:
Your one-to-many association between TestSample and Part is not bidirectional, the mappedBy is not correct (the application table is not owning the relation, it is not even aware of test_sample), your mapping doesn't make sense. There is something to change.
I think that you should show what the expected tables are, not the generated one (since the mappings are incoherent, the generated result can't be satisfying). You are talking about compromise so I believe that you have an idea of what the expected result should be. Please show it.

Categories