Hibernate - User and his Report list - java

I have an entity named User. I decided that every user has its `Report (also entity) list. Then I created something like this:
#Entity
#Table(name="user")
public class User implements Serializable{
#Id
#GeneratedValue
private long id;
...
#ElementCollection
private List<Report> reportList;
Now the Report object looks like this:
#Entity
#Table(name = "report")
public class Report implements Serializable{
#Id
#GeneratedValue
private long id;
...
#ManyToOne
private User reporter;
Is it right approach? Every user can have many reports, but every report can belong to only one user.
I thought about changing #ElementCollection to #ManyToOne, but I think I prefer operating on a list.
The main problem is, how to make relation(connection) between user and their reports?

No, it's not the right approach. ElementCollection is for a collection of simple types (String dates, etc.) or embeddable types. For a collection of entities, you need a OneToMany association (which is quite normal, given that you have a ManyToOne in the other direction):
one user has many reports
many reports are reported by one user
.
#OneToMany(mappedBy = "reporter")
private List<Report> reportList;
The documentation covers this in detail.

Related

Java ORM vs multiple entities

I have a class Health Check - as part of the class I record how many parasites are seen (enum NONE, SOME, MANY) and also the location of the parasites (enum HEAD, FEET, BODY).
Two ways this could be done:
METHOD 1
Health Check
#Entity
public class HealthCheck {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
private Parasite parasite;
Parasite
public class Parasite {
private BodyLocation bodyLocation;
private Coverage coverage;
}
Or I could have:
METHOD 2
#Entity
public class HealthCheck {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
private ParasiteNumber parasiteNumber;
private ParasiteLocation parasiteLocation;
Would method 1 require #Entity on parasite class and a entry in the table for each Health Check and a #OneToOne annotation?
Note Parasite class is not used anywhere else. Its only a part of a Health Check.
Which way is correct?
Generally speaking, yes. In ORM (aka JPA or Hibernate), you are building a graph of objects that represent things in your database. Anything that one #Entity touches is also an #Entity because it's a graph.
Whether it's a #OneToOne or a #ManyToOne, etc, depends on the nature of your data model. But, keep in mind, those connections can also be #Lazy, so they are not loaded until they are needed.
Because of #Lazy, method 2 might be preferred, idk. I assume ParasiteLocation and ParasiteNumber is some sort of join-table. If that's the case, you could load a HealthCheck with its PL and PN, but those objects could be Lazy to Parasite.
I don't think there is a one-size-fits-all answer to your question. It very much depends. But good news, ORM is flexible to cover any/all scenario you might have.
If Parasite is only used in HealthCheck class,which can be seen as an association.
Association means that the existence of child class is dependent on the existence of the parent so it has no independent lifecycle ,thus you can either declare the attributes directly in HealthCheck as you did in your second example ,or you can declare them in Parasite class and then make it Embeddable inside the HealthCheck class,e.g:
/*To embed a class inside in Entity you must declare it Embeddable via the JPA
annotation #Embeddable */
#Embeddable
public class Parasite {
#Column(name="body_location")
private BodyLocation bodyLocation;
#Column(name="coverage")
private Coverage coverage;
}
#Entity
public class HealthCheck {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
#Embedded
private Parasite parasite;
}
Here your HealthCheck db table will have the attributes specified in the Parasite class,and note that Parasite table won't be created since it is Embedded and not an Entity (#Entity).
Hope this helps!

JPA/Hibernate: save an Entity that holds list of Entities that hold a 2D array of objects in a object hierarchy

I am trying to persist my Game class, structured as follows:
An class Game, which holds a list of Boards
The Board Entity class a 2D array of Fields, representing a playing board.
The Field class is abstract and has concrete subclasses such as Floor, Goal and Wall.
What I would like to have is a One-to-many relationship beween Game and Board Entities, and the Fields in the Board class as an embedded property. To embed this I'd like to write my own mapping code to turn the 2D array into a String of symbols representing the fields, but if that's not possible then I'd save the hierarchy using the single-table strategy.
I'm still learning JPA/Hibernate so there's a bit too much going on at once for me to fully grasp how to tackle this.
This is what I've tried:
Implementing an AttributeConverter for Board
I learned that this is not possible because AttributeConverters cannot be used for relationship attributes (JPA spec).
Implementing an AttributeConverter for Field[][], and making it an Embedded property
#Entity
#NoArgsConstructor
public class Game {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Getter
private Long id;
#OneToMany(mappedBy = "game", cascade = CascadeType.ALL)
#Getter
private List<Board> boards = new ArrayList<>();
...
}
#Entity
public class Board {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#ManyToOne()
#JoinColumn(name="game_id")
private Game game;
#Embedded
#Convert(converter = FieldsConverter.class, attributeName = "field")
private Field[][] fields;
with FieldsConverter being
#Converter
public class FieldsConverter implements AttributeConverter<Field[][], String> {
IntelliJ already gives a "Embedded atribute type should not be Field[][]" error, running results in a weird ArrayIndexOutOfBoundsException (see https://pastebin.com/ZVbCGaGA for full stacktrace).
Writing the JDBC mapping myself
I also started writing a BoardRepository myself, in which I manually did the mapping of the Fields attribute, but then I remembered that then I'd probably have no managed relationship between Game and Board, or the lazy loading that JPA would provide, so I stopped before I was fully ready.
One thing I read that comes close to a solution is this: https://vladmihalcea.com/multidimensional-array-jpa-hibernate/, but I'm not sure how to adapt this to an Embedded attribute and the object actually being part of an object hierarchy.
Could anyone please give me some pointers on how to proceed?

Java Hibernate many-to-many not fully updating

I am still fairly new to Hibernate and I am still on a steep learning curve.
I have an application that will track which people were on which event and visa-versa. I have an Event Class and a Person Class linked via a jointable.
I have forms and helper classes that allow me to enter the data on the separate Person and Event classes, persist it, search it, delete it, change it and list it. This is all tested and working.
When I add people to the event I can list all the events and see the list of people attached to the events but when I output the list of People they all have an Event list of size 0.
It is my understanding that if I attach a person to an event that the person should show up in Event.myPeople and that the event should show up in Person.eventList.
Clearly I am doing something wrong and I suspect that it is in my annotations and Declarations. I have listed both set for Event and Person classes below. Failing that I have a fundamental misunderstanding of Hibernate ,both are likely. On the bright side, the more mistakes I make the faster I learn.
Any idea where I am going wrong?
#Entity
#Table(name = "PERSON")
public class Person implements Serializable {
#Id
#GeneratedValue
#Column(name = "person_id")
private int ID;
private String foreName;
private String surName;
#Temporal(javax.persistence.TemporalType.DATE)
private Date dob; //used to differentiate people with same name
#Temporal(javax.persistence.TemporalType.DATE)
private Date joinDate; //used to filter events outside active dates
#Temporal(javax.persistence.TemporalType.DATE)
private Date endDate; //used to filter events outside active dates
private Boolean active;
#ManyToMany()//cascade=CascadeType.ALL)
#JoinTable(name = "PERSON_EVENT", joinColumns = #JoinColumn(name = "person_id"),
inverseJoinColumns = #JoinColumn(name = "event_id"))
private Set<Event> eventList;
#OneToOne
private Sections mySection;
#Entity
#Table(name = "EVENT")
public class Event implements Serializable {
#Id
#GenericGenerator(name = "generator", strategy = "increment")
#GeneratedValue(generator = "generator")
#Column(name="event_id")
private long id;
private String eventTitle;
private String eventDescription;
private String eventLocation;
#Temporal(javax.persistence.TemporalType.DATE)
private Date startDate;
#Temporal(javax.persistence.TemporalType.DATE)
private Date endDate;
#ManyToMany(cascade = CascadeType.ALL)
private Set<Person> myPeople;
#OneToMany(mappedBy = "myEvent")
private Set<EventType> type;
There is a common misconception about bidirectional relations in Hibernate. Hibernate does not care about consistency in your object tree. If there is a bidirectional relation between events and persons, you have to add the person to the event and the event to the person yourself. Hibernate only persists what you created in memory. Do not expect Hibernate to add any object to any collections, this is the responsibility of the business logic, which shouldn't rely on Hibernate to work properly.
Now, bidirectional relations are special in that inconsistent states in memory cannot even be persisted. With consistent data, Hibernate only has to persist one site of the bidirectional relation, because the other is (or should be) redundant. This is done by marking one part as the "inverse" part. (I'm sorry that I don't know annotation mapping syntax well enough to point to a possible error in you mapping.) "inverse" means to Hibernate nothing more then "ignore when syncing to database", because it is expected to be redundant.
You still have to make sure that the information in both collections are redundant. It actually "works" when you only add the items to the non-inverse collections. But, however, this is not recommended to do because the objects will not be consistent until saved and loaded into a new session.
Also make sure that the bidirectional relation is mapped to the same table using the same foreign keys. I don't know if annotation mapping does detect this automatically.
Hope that helps.
The problem must be due to a missing mappedBy field in the many to many associations.
The field that owns the relationship is required unless the relationship is unidirectional.
I think adding (mappedBy = eventList) will suffice.
You can try to use: #ManyToMany(fetch = FetchType.LAZY)

How to add list of Strings to entity - Spring, Hibernate

I have Spring Boot project with DB. I've already get table, let's call it "People". I need to add a second table "People_Strings" with two columns: People_id and String. I need to include many strings for every row from People.
How can I map it in my People entity in project?
Edit: I need to do this without creating separete class for String or for People_Strings
If you only need that, you can add the following property to the People entity class:
#ElementCollection
public List<String> strings;
What you need is #OneToMany relation between people and strings. Something like the following will work for you.
#Entity
#Table(name = "People")
public class People{
#Id
#GeneratedValue
private Long id;
#OneToMany(fetch= FetchType.EAGER, cascade=CascadeType.ALL, mappedBy = "peopleId")
private List<PeopleStrings> PeopleStrings;
#Entity
#Table(name = "People_Strings")
public class PeopleStrings{
#Id
#GeneratedValue
private Long peopleId;
#ManyToOne
#JoinColumn(name="peopleId")
private String string;
You'll want a #OneToMany relationship on the Person object (please don't use plurals, eg People). And possibly a #ManyToOne relationship on the PersonString object (again no plurals)
https://docs.jboss.org/hibernate/jpa/2.1/api/javax/persistence/OneToMany.html
https://docs.jboss.org/hibernate/jpa/2.1/api/javax/persistence/ManyToOne.html
This is kinda basic question and I suggest you to read Hibernate "Get Started" first. (one-to-many / many-to-one relations especially)

Is it possible to force Hibernate to embed an Entity?

In my use-case, I would like to #Embedded a class C in an entity.
Another entity refers to C with #OneToMany association and therefore C is annotated with #Entity.
I am aware that this seems like bad design, yet I believe that it makes perfect sense in my case.
Is it possible to force Hibernate to embed an Entity? If I try it, Hibernate complains about a missing setter for the id property of C.
I think the problem comes from this:
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
Why not just create the entity that you want, and in that entity, embed C as well. That way you have C in both classes, one as embedded and another as embedded of the new entity.
#Embeddable
public class Contact {
private String firstname;
private String lastname;
// getters and setters removed.
}
and here is your embedding class:
#Entity
public class Student {
#Embedded
private Contact contact;
}
and here is the new entity that embeds contact also
#Entity
public class FirmContact {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int contactId;
#Embedded
private Contact contact;
}
And finally the class that insists the contact must be an entity:
#Entity
public class Business {
#OneToOne(cascade=CascadeType.ALL)
private FirmContact contacts;
}
It'll just be a couple of extra steps in java to populate the object, but it should do the mapping you want. I hope this helps.
Hibernate doesn't allow you to treat an Embeddable as an Entity or to embed an Entity. According to Hibernate types:
an Embeddable, doesn't have an identifier, since it's state is part of an owning Entity.
an Entity cannot be embedded, because each Entity has a distinct life-cycle.
Since another class already has a #OneToMany association to class C, it's obvious you cannot turn it into an Embeddable.
More, a bidirectional #OneToMany association will perform better than an embeddable collection.
What you can do, is to use it as a #OneToOne association in the entity where you wanted to embed the C entity. You can make that target entity be the owning side of the association so that the C association is bound to the target entity life-cycle.

Categories

Resources