Spring Boot + JPA (Hibernate) - how to update some fields - spring-boot

I have to update my entity - when placeA or/and placeB is updated, I have to also update points.
SO I fetch route object from database and modify one or two fields (placeA, placeB). The problem is that I have to update points accordingly -> in Point object I have to also update pointAddress (point.pointAddress has to be updated to route.placeA or route.placeB values):
public class Route{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "place_a_id")
private Address placeA;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "place_b_id")
private Address placeB;
#OneToMany(mappedBy = "route", cascade = CascadeType.ALL)
List<Point> points;
public class Point{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "point", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<PointDetail> pointDetails;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "route_id", nullable = false)
private Route route;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "route_address_id", nullable = false)
private Address pointAddress;
public class PointDetail{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "point_id", nullable = false)
private Point point;
And I fetch Route entity from db and from now this object is in persistent state (everything within the same transaction) so I don't need to invoke repository.save(myChangedRoute) explicitly.
The question is how to update route.points[0].pointAddress?
Is it sufficient?
Route route = repository.findRouteById(1L);
route.setPointA("new place");
route.getPoints().get(0).setPointAddress("new place")
And what with route.getPoints().get(0).getRoute() and route.getPoints().get(0).getPointDetails() objects? For example should I also update route.getPoints().get(0).getPointDetails() object? PointDetail objects have a field point that maybe should be also updated?
I have related (nested) objects in my Route object (dependencies) so my question is how to update my object structure properly to not overwrite new values with the old nested ones that are not updated, e.g. I updated:
route.getPoints().get(0).setPointAddress("new place")
but I haven't updated route.getPoints().get(0).getPointDetails().get(0).setPoint(MY NEW UPDATED AND NOT YET SAVED Point object)???
SO we have a circular dependencies route -> point -> pointDetail -> point and the question is if it's sufficient to update only pointAddress in my route.point object or I have to also update pointAddress in route.point.pointDetail.point?

First of all: point -> pointDetail -> point is not a curricular dependecny. The relation between pointDetail and point is BiDirectional dependency.
PointDetail objects have a field point that maybe should be also updated? Of course not.
and the question is if it's sufficient to update only pointAddress in my route.point? Yes that is enough.

Hibernate has the first level cache it ensures that objects will be loaded only once per session. So no override can occur. Of course, is up to you and your code how will you update properties and in which order.

Related

I seem to need both #OneToOne and #OneToMany relationship

I'm writing an app to operate a collection of hardware that is supposed to be setup and shutdown repeatedly in different locations.
I want to track those installations so I've created separate entities for a physical object itself and the installation.
Station object needs to keep track of up to one (null while not installed) active installation information (so #OneToOne) but also all the previous installations (so #OneToMany)
#Entity
#Table(name = "station")
class Station{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "stationInstallation", cascade = CascadeType.DETACH,
CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH)
#JoinColumn(name = "station_installation_id", nullable = true)
private StationInstallation activeStationInstallation;
...
#OneToMany(fetch = FetchType.Lazy, mappedBy = "station_id", cascade =
{CascadeType.DETACH, CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REFRESH })
private List<stationInstallation> stationInstallations;
...
}
The other entity will have the station id, location and date of the setup as well as the shutdownDateTime being null.
#Entity
#Table(name = "stationInstallation")
class StationInstallation{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy ="stationInstallation", cascade - CascadeType.ALL)
private Station station;
#Column(name = "location")
private String location;
#Column(name = "setupDateTime")
private LocalDateTime setupDateTime;
#Column(name = "shutdownDateTime")
private LocalDateTime shutdownDateTime
}
Finally the shutdown method is supposed to unattach the entity from the Station entity by setting activeStationInstallation to null and setting shutdownDateTime to LocalDateTime.NOW.
...
stationInstallation.getStation().setActiveStationInstallation(null);
stationInstallation.setShotdownDateTime(LocalDateTime.NOW);
...
But that will obviously result in a growing number of StationInstallation "finished" objects, which would seem to require #ManyToOne relation with the Station all the while I want to keep #OneToOne relation with the activeStationInstallation.
What do?
I figured I could just make another entity calling it FinishedStationInstallation, remove shutdownDateTime from StationInstallation, make both immutable and instead of adding shutdownDateTime with a setter add it in the constructor while deleting the active version. And immutablity is an asset, ut at the same time I'd have to add several new tables to the db and keep track of and query two entities instead of one.
EDIT: I guess I could get rid of List stationInstallations from the 1st entity, since I don't really need to keep the track of it beyond db queries, but I added it for the clarity of the question.
I would model it this way:
#Entity
#Table(name = "station")
class Station{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.DETACH,
CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH)
#JoinColumn(name = "station_installation_id", nullable = true)
private StationInstallation activeStationInstallation;
...
#OneToMany(fetch = FetchType.Lazy, mappedBy = "station", cascade =
{CascadeType.DETACH, CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REFRESH })
private List<stationInstallation> stationInstallations;
...
}
#Entity
#Table(name = "stationInstallation")
class StationInstallation{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne(mappedBy ="stationInstallation", cascade = CascadeType.ALL)
private Station station;
#Column(name = "location")
private String location;
#Column(name = "setupDateTime")
private LocalDateTime setupDateTime;
#Column(name = "shutdownDateTime")
private LocalDateTime shutdownDateTime
}
The one-to-one relation from Station to installation has a FK => so no mapped by
The one-to-many relation from Station to installation is mapped by the station association on installation, which obviously has to be a many-to-one association, since the other side is a one-to-many

Hibernate mapping user relation to entities

Let's se we have Hibernate entity User with basic fields such as username, password, roles etc..
Now we have an entity such as Car.
User has a OneToOne relationship with Car, cause he can own a car. But he also has besides this a OneToMany relationship to Car, because he also owns the cars of his children. But in the frontend I want to know which cars he owns for himself and which cars he owns for his children. The same applies to the relationship between User and motorbike (his own, his childrens, etc...)
How would the User entity class look like? Is it good to have the relationships mapped in an "Helper" entity such as UserData:
#Entity
#Data
#Table( name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username")
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 150)
private String username;
#NotBlank
#Size(max = 120)
private String password;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_DATA_ID")
private UserData userData;
UserData:
#Entity
#Data
#Table( name = "user_data")
public class UserData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "OWN_CAR_ID")
private Car ownCar;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "PARTNER_CAR_ID")
private Car partnerCar;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_children_cars",
joinColumns = #JoinColumn(name = "user_data_id"),
inverseJoinColumns = #JoinColumn(name = "car_id"))
private Set<Car> childrenCars = new HashSet<>();
public boolean addToChildrenCarSet(Car c) {
return childrenCars.add(c);
}
public UserData() {
}
}
As you ask for an opinion, I would say it gets unnecessary complicated if you use the intermediate entity user_data. :-) There is no real drawback to add more fields and keys into the user class - performance is probably also better then using the EAGER fetching. If performance is an issue, better optimize querys later on then splitting the table now.
Also the #ManyToMany I would avoid - better create the intermediate table and relations yourself. You can check out https://bootify.io and create your database schema there. There is no EAGER fetching and also no CascadeType.ALL (both only good ideas in special cases), you would probably add more problems with that then actual helping in any way.
So the addToChildrenCarSet method would end up in a #Service class, in a method with #Transactional, in my proposal.

OneToMany does not return values saved from other entity

I have entity structure:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
List<UserAgreement> userAgreements= new ArrayList<>();
}
#Entity
#Table(name = "user_agreements")
public class UserAgreement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "agreement_id")
private Agreement agreement;
}
#Entity
#Table(name = "agreements")
public class Agreement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "agreement", cascade = { CascadeType.ALL })
List<UserAgreement> userAgreements = new ArrayList<>();
}
I am using Spring Boot with JpaRepository. When I use AgreementRepository extends JpaRepository<Agreement, Long> to save Agreement and related UserAgreement, it works well and cascades necessary fields to DB:
agreement.getUserAgreements().add(new UserAgreement(user, agreement, status));
agreementRepository.save(agreement);
However, after save, if try to retrieve user.getActiveUserAgreements(), I get empty list. It does not refresh.
How to force User entity to get List<UserAgreement> which was saved from other side?
From the Wikibooks: OneToMany
The relationship is bi-directional so, as the application updates one
side of the relationship, the other side should also get updated, and
be in sync. In JPA, as in Java in general it is the responsibility of
the application, or the object model to maintain relationships. If
your application adds to one side of a relationship, then it must add
to the other side.
That means you need to assign the UserAgreement to the User when you create the relation.
It looks like many-to-many association. You might probably drop UserAgreement class. Anyway, to support it you have to write helper methods addAgreement(), removeAgreement() etc. See more details here https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/

Cannot delete or update parent row Hibernate

I have a design and scenario entity.
I'm getting an error when removing a Design that contains one or more scenarios.
The design entity looks like:
#Entity
public class Design {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(columnDefinition = "LONGBLOB")
private byte[] image;
#OneToMany(mappedBy = "design", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Scenario> ScenarioSet;
The scenario entity looks like:
#Entity
public class Scenario {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "design_ID")
private Design design;
As you can see A design can have more than one scenarios.
And Design is responsible for the relation.
My code to save a scenario:
Design design = this.designService.getDesignById(designID);
scenario.setDesign(design);
this.scenarioService.saveScenario(scenario);
Saving it isn't a problem. I'm saving it this way because the scenario doesn't have an ID at first.
The error i'm getting:
Cannot delete or update a parent row: a foreign key constraint fails (`db`.`scenario`, CONSTRAINT `FKqmttw6jic4aplswy08wtkj5r7` FOREIGN KEY (`design_id`) REFERENCES `design` (`id`)) 0.016 sec
This lets me think that It isn't cascading when I remove the Design.
Add orphanRemoval=true to your scenario list mapping in the Design entity:
#OneToMany(mappedBy = "design", cascade = CascadeType.ALL, orphanRemoval=true, fetch = FetchType.LAZY)
private Set<Scenario> ScenarioSet;
CascadeType.ALL (or precisely CascadeType.REMOVE) serve for cascading remove operation when you take an item from the collection and save the owning entity (Design in this case). To tell Hibernate to remove items in the collection when the owning entity (Design) is removed, you need to use the orphanRemovalattribute:
https://docs.oracle.com/cd/E19798-01/821-1841/giqxy/

Spring JPa Id empty

I have an entity who own many object
#Entity
public class Lodger implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long lodgerId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "lodger")
private List<IdentityCard> identityCardList;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "lodger")
private List<Phone> phoneList;
...
}
#Entity
public class IdentityCard {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long identityCardId;
private String identyCardValue;
#OneToOne
#JoinColumn(name = "identity_card_type_id") //without -> identity_card_type_identityCardTypeId
private IdentityCardType identityCardType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "lodger_id")
private Lodger lodger;
}
When i save my lodger, all my object is saved (identiyCard, phone), but their field lodger_id is null.
I was thinking it was supposed to be done automatically when we use cascadeType.all.
The owner side of the bi-directioinal associations are in IdentityCard and Phone entities, this is the same as saying that the mappedBy is in Lodger's associations.
So for the persistence of the links you must set the lodger attribute in IdentityCard and Phone entities. Isn't necessary to add this entities to the Lodger's collections but it is fine because you want to save this entities along with Lodger using cascade option.

Resources