JpaRepository delete child elements - spring

I'm trying to do a very simple delete operation, but somehow it doesn't work since I updated the DAOs to JpaRepository. Basically it's this:
A a = aRepository.findOne(id);
a.setSomeField("someNewString");
List<B> bList = a.getBs();
bList.clear();
aRepository.saveAndFlush(a);
The field get's updated as expected, but the bList stays unchanged. I've even tried:
A a = aRepository.findOne(id);
a.setSomeField("someNewString");
List<B> bList = a.getBs();
for(B b : bList) {
bRepository.delete(b);
}
bRepository.flush();
bList.clear();
aRepository.saveAndFlush(a);
Still the same...
Class A looks like this:
#Entity
#Table(name = "A")
public class A implements Serializable {
private static final long serialVersionUID = -1286451120913657028L;
#Column(name = "id", length = 16, nullable = false, updatable = false)
#GenericGenerator(name = "uuid", strategy = "uuid2")
#GeneratedValue(generator = "uuid")
#Basic(fetch = FetchType.EAGER)
#Id
protected UUID id;
#OneToMany(mappedBy = "a", fetch = FetchType.EAGER)
#Cascade({ CascadeType.ALL })
List<B> bList;
// getter + setter
}
What am I doing wrong?!
Class B:
#Entity
#Table(name = "B")
public class B implements Serializable {
#Column(name = "id", length = 16, nullable = false, updatable = false)
#GenericGenerator(name = "uuid", strategy = "uuid2")
#GeneratedValue(generator = "uuid")
#Basic(fetch = FetchType.EAGER)
#Id
protected UUID id;
#ManyToOne(optional = false)
#JoinColumns({ #JoinColumn(name = "A_id", referencedColumnName = "id", nullable = false) })
#Valid
A a;
// setter + getter
}
Setters and getters are all just as simple as possbile:
public List<B> getBList() {
return bList;
}
public void setBList(List<B> bList) {
this.bList = bList;
}
Some more information:
spring 3.2.2
hibernate 4.2.2
spring-data-commons 1.5.1
spring-data-jpa 1.3.2

Update the A.bList property as follows:
public class A {
#OneToMany(mappedBy = "a", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
List<B> bList;
}
The orphanRemoval = true annotation attribute will tell the underlying JPA implementation to delete B records which don't have any parent left.
Also, Since the B side manages the association, you should clear its a attribute when breaking the relationship. To make this easier to read and to remove the burden of such implementation details from the caller, you should introduce management methods in A :
public class A {
public void clearBList() {
for (B b : bList) {
b.releaseA();
}
bList.clear();
}
}
public class B {
void releaseA() {
this.a = null;
}
}
You should avoid exposing collections directly and instead return an immutable version of it to prevent clients to the A class of modifying the collection directly without the A class knowing it. A manages the B list, so it should have full control over it!
public class A {
public List<B> getBList() {
return Collections.unmodifiableList(bList);
}
}
Hope that helps.

Related

Spring Boot, Hibernate, bidirectional One-To-Many. Strange behaviour. Why is there two selects insdead of an error?

Spring Boot, Hibernate, bidirectional One-To-Many. Strange behavior. Why is there two selects instead of an error?
I have a basic Spring boot application.
It simulates throwing dices.
I have two entity classes Dice and DiceBatch.
DiceBatch has List<Dice> dices;
Dice has DiceBatch diceBatch; as two sides of bidirectional ManyToOne, or OneToMany.
I use JpaRepository<DiceBatch, UUID> to get one instance of DiceBatch by callig a method of JpaRepository findById(UUID id)
I call this method inside DiceBatchService's method findDiceBatchById(UUID diceBatchId).
Method is marked as #Transactional.
When i do that Hibernate logs one SQL select:
/* select
d
from
DiceBatch d
where
d.id = ?1 */ select
dicebatch0_.dice_batch_id as dice_bat1_1_,
dicebatch0_.batch_creation_time as batch_cr2_1_,
dicebatch0_.batch_name as batch_na3_1_
from
dice_batch dicebatch0_
where
dicebatch0_.dice_batch_id=?
At this point everything is ok.
Method returns DiceBatch entity with lazily initialized List<Dice> dices.
This is important. Method is #Transactional when method returns I should leave transactionla context.
Lazy fields should stay lazy and should cause LazyInitializationException if I try to access them.
Now control goes back to the controller method of DiceBatchController findDiceBatchById(UUID diceBatchId)
And here something strange happens.
Hibernate logs another select
select
dices0_.dice_batch_id as dice_bat5_0_0_,
dices0_.dice_id as dice_id1_0_0_,
dices0_.dice_id as dice_id1_0_1_,
dices0_.dice_batch_id as dice_bat5_0_1_,
dices0_.sequential_number as sequenti2_0_1_,
dices0_.throw_result as throw_re3_0_1_,
dices0_.throw_time as throw_ti4_0_1_
from
dice dices0_
where
dices0_.dice_batch_id=?
...and response JSON contains DiceBatch with all Dice entities related to it.
So I have several question.
Why didn't I get LazyInitializationException?
How come the List<Dice> inside DiceBatch got initialized outside of Transactional context?
How Spring managed to build a complete entity of DiceBatch, including the content of the List<Dice> without any exceptions?
How to modify my code to avoid this strange implicit bahavior?
Here is all the relevant code.
package org.dice.model;
#Entity
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Dice {
#Id
#GenericGenerator(name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator")
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO,
generator = "UUID")
#Column(name = "dice_id",
nullable = false)
private UUID id;
#Column(name = "throw_result",
nullable = false)
private Integer throwResult;
#Column(name = "throw_time",
nullable = false)
private LocalDateTime throwTime;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "dice_batch_id",
nullable = false,
foreignKey = #ForeignKey(name = "fk_dice_dice_batch_id_dice_batch_dice_batch_id")
)
#JsonBackReference
private DiceBatch diceBatch;
#Embedded
private SequentialNumber sequentialNumber;
}
package org.dice.model;
#Entity
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class DiceBatch {
#Id
#GenericGenerator(name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator")
#GeneratedValue(strategy = GenerationType.AUTO,
generator = "UUID")
#Column(name = "dice_batch_id",
nullable = false)
private UUID id;
#Column(name = "batch_name",
nullable = false)
private String batchName;
#Column(name = "batch_creation_time",
nullable = false)
private LocalDateTime batchCreationTime;
#OneToMany(
mappedBy = "diceBatch",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
#JsonManagedReference
private List<Dice> dices = new ArrayList<>();
public void addDice(Dice dice) {
dices.add(dice);
dice.setDiceBatch(this);
}
public void removeDice(Dice dice) {
dices.remove(dice);
dice.setDiceBatch(null);
}
}
package org.dice.repo;
#Repository
public interface DiceBatchRepo extends JpaRepository<DiceBatch, UUID> {}
package org.dice.service;
#Service
#RequiredArgsConstructor
public class DiceBatchService {
#Transactional
public DiceBatch findDiceBatchById(UUID diceBatchId) {
DiceBatch diceBatch = diceBatchRepo
.findById_my(diceBatchId)
.orElseThrow();
return diceBatch;
}
}
package org.dice.controller;
public class DiceBatchController {
#GetMapping(path = "/get/{diceBatchId}")
public ResponseEntity<DiceBatch> findDiceBatchById(
#PathVariable(name = "diceBatchId") UUID diceBatchId) {
log.info("<C>[/batch/get] endpoint reached.\n" +
"Dice Batch Id: {}\n",
diceBatchId);
return ResponseEntity.ok(diceBatchService.findDiceBatchById(diceBatchId));
}
}

Jackson #JsonIgnoreProperties seems not to work all the time

I mapped two entities to those following classes :
#Getter
#Entity
#Table(name = "users")
#JsonIdentityInfo(generator = PropertyGenerator.class,
property = "id")
#SequenceGenerator(name = "id-generator", sequenceName = "seq_users")
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
#NoArgsConstructor(access = PROTECTED)
#RequiredArgsConstructor
public class User extends IdentifiedById {
#Include
#NonNull
#Column(name = "email_address", unique = true)
private String emailAddress;
#Setter
#JsonIgnore
private String hash;
#Setter
private boolean admin;
#OneToMany(
mappedBy = "user",
orphanRemoval = true,
cascade = ALL
)
#JsonIgnoreProperties("user")
private Set<Cart> carts;
{
carts = new HashSet<>(0);
}
}
#Getter
#Entity
#Table(
name = "carts",
uniqueConstraints = #UniqueConstraint(
columnNames = {
"creation_time",
"user_id"
}
)
)
#JsonIdentityInfo(generator = PropertyGenerator.class,
property = "id")
#SequenceGenerator(
name = "id-generator",
sequenceName = "seq_carts"
)
#EqualsAndHashCode(
callSuper = false
)
#RequiredArgsConstructor
#NoArgsConstructor(access = PROTECTED)
public class Cart extends IdentifiedById {
#NonNull
#Column(name = "creation_time")
private LocalDateTime creationTime;
#NonNull
#ManyToOne(cascade = ALL)
#JoinColumn(
name = "user_id",
referencedColumnName = "id"
)
#JsonManagedReference
private User user;
#Exclude
#JsonProperty("productStoreQuantities")
#JsonSerialize(converter = AdditionConverter.class)
#OneToMany(mappedBy = "cart", orphanRemoval = true, cascade = ALL)
private Set<Addition> additions;
{
additions = new HashSet<>(0);
}
}
If I retrieve a user, its carts do not contain its reference, it is fine by me.
Now from a rest endpoint perspective I would like not to serialize users along with their carts if one requests multiple users like so :
**/api/users -> {"id":1, "emailAddress":"test#test.test", "admin": false}**
**/api/users/1 -> {"id":1, "emailAddress":"test#test.test", "admin": false, "carts": [...]}**
Thus, I created a wrapper class named Users containing a list of users annotated with #JsonValue and #JsonIgnoreProperties("carts") :
#RequiredArgsConstructor
public class Users implements Serializable, List<User> {
#Delegate
#JsonValue
#JsonIgnoreProperties("carts")
private final List<User> values;
}
I don't know why but carts keep being serialized, I heard that #JsonIgnoreProperties does not work on collections and arrays but it does in my first case.
You should use JsonIgnoreProperties in a class level.
This is well explained in this post
https://www.baeldung.com/jackson-ignore-properties-on-serialization

Spring boot domain class required for mapping table

I m new to Spring Boot. I have a table (Team) that has resources, am storing in a separate table (Resources) and have team_resource mapping table (with fields teamid, resourceid). My question is should I have a domain class for the mapping_table too ?
When I m inserting a new team (POST) with resources I create entry in all 3 tables. I m using facade/dao pattern for writing/ reading to the DB. I have to handle when the team is modified/ deleted. Should I have a domain class for the mapping_table?
There are multiple approaches you can handle it
Approach 1
Define #ManyToMany between your Team and Resources entity like this:
In Team Entity
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "resources",
joinColumns = { #JoinColumn(name = "id") },
inverseJoinColumns = { #JoinColumn(name = "id") })
private Set<Resources> resources= new HashSet<>();
In your resources entity:
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "resources")
private Set<Team> teams= new HashSet<>();
Approach 2
#Entity
#Table(name = "team_resources")
public class TeamResources implements Serializable {
#EmbeddedId
private TeamResourcesId id;
#ManyToOne
#JoinColumn(name = "team_id", referencedColumnName = "id", insertable = false, updatable = false)
private Team team;
#ManyToOne
#JoinColumn(name = "resources_id", referencedColumnName = "id", insertable = false, updatable = false)
private Resources resources;
public TeamResources (Team u, Resources r) {
// create primary key
this.id = new TeamResourcesId (u.getUserId(), q.getQuestionId());
// initialize attributes
this.user = u;
this.question = q;
}
#Embeddable
public static class TeamResourcesId implements Serializable {
#Column(name = "team_id")
protected Long teamId;
#Column(name = "resources_id")
protected Long resourcesId;
public TeamResourcesId () {
}
public TeamResourcesId (Long teamId, Long resourcesId) {
this.teamId= teamId;
this.resourcesId= resourcesId;
}
//Getter , setter. equal and hash
}
so to answer your question, follow second approach and its good to not define bidirectional approach as it can lead to some run time problem if not handled properly.

composite pattern, I am not able to retrieve all entities from the backend

I am trying to implement and use the composite pattern in my system.
The problem is that I cant retrieve all the hierarchy of entities from the backend.
I am not sure what is the problem, the fetch is fine. So, I am not sure if is hibernate.
Lets see, these are my entities.
#Entity
#Table(name = "game")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Document(indexName = "game")
public class Game extends AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Column(name = "name", nullable = false)
private String name;
#Column(name = "detail")
private String detail;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "rule_id")
private GameRule gameRule;
...
In this class I save the main "GameRule"
#Entity
#Table(name = "game_rule")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue("rule")
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "_class")
#JsonSubTypes({
#JsonSubTypes.Type(value = SimpleRule.class, name = "SimpleRule"),
#JsonSubTypes.Type(value = CompositeRule.class, name = "CompositeRule") })
public abstract class GameRule implements Serializable {
private static final long serialVersionUID = -4597791997254248990L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false)
private Long id;
private String operator;
In this class I save a list of GameRules
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue("group")
public class CompositeRule extends GameRule {
private static final long serialVersionUID = 6197786758476721324L;
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
#JoinTable(name = "game_rules_hierarchy",
joinColumns = #JoinColumn(name = "parent_rule_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "child_rule_id", referencedColumnName = "id"))
#OrderBy("id")
private List<GameRule> rules;
public List<GameRule> getRules() { return rules; }
public void setRules(List<GameRule> rules) { this.rules = rules; }
And now the leaf entity.
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue("simple")
public class SimpleRule extends GameRule {
private static final long serialVersionUID = 6197786758476721324L;
private String variable;
private Double value;
#ManyToOne
#NotNull
private Device device;
Now, the restController to retrieve the data
#RequestMapping(value = "/games/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
#Transactional
public ResponseEntity<Game> getGame(#PathVariable Long id) {
log.debug("REST request to get Game : {}", id);
Game game = gameRepository.findOne(id);
return Optional.ofNullable(game)
.map(result -> new ResponseEntity<>(
result,
HttpStatus.OK))
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
Now in the view I am able to receive the CompositeRule objects but nothing about the simpleRule objects.
From the UI (angular), I am using this JSON to load the hierarchy.
vm.game.gameRule = {id: null, operator: "", type:null, _class:"CompositeRule",
rules: [ {id: null, operator: "", type:null, _class:"CompositeRule", rules:
[{id: null, type:null, _class:"SimpleRule", device: "6", variable: "POWER", operator: ">", value: "100"},
{id: null, type:null, _class:"SimpleRule", device: "6", variable: "POWER", operator: ">", value: "100"}]}
]};
This is loaded succesfully in the DB. But the problem is when I try to retrieve the entire hierarchy. Currently I am receiving only the CompositeRule objects, somthing like:
vm.game.gameRule = {id: 1, operator: "", type:null, _class:"CompositeRule",
rules: [ {id: 2, operator: "", type:null, _class:"CompositeRule", rules:[]} ]};]
In the RestController I am able to see all the hierarchy objects. I am not sure what the problem is.
Thanks for reading.
MY BAD!
Everything was fine. My problem is in the console logs. It seems that the console in chrome is not showing all the hierarchy. But if I go to the network view I am able to see everything.
Thanks.

Unable to save data to composite Table Via Spring Data rest json post

I have 3 Tables in db
training
- training_id (pk)
user_profile
- profile_id (pk)
-training_profile (composite table)
- training_id
- profile_id
I have already record in user_profile table having profile_id=44 and want to create new record for training table ,and also to associate this new training with already existing user_profile record which has id 44,but after post data is saved to training table but it is not inserted into lookup table user_training.
My Object Classes Are
- Training Class
#Entity
#Table(name = "training", schema = "public")
public class Training implements java.io.Serializable {
#Id #GeneratedValue
#Column(name = "training_id", unique = true, nullable = false)
private Long trainingId;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "trainings")
private Set<UserProfile> userProfiles = new HashSet<UserProfile>(0);
#Column(name = "training_subject", length = 200)
private String trainingSubject;
public Training() {
}
public Long getTrainingId() {
return this.trainingId;
}
public void setTrainingId(Long trainingId) {
this.trainingId = trainingId;
}
public String getTrainingSubject() {
return this.trainingSubject;
}
public void setTrainingSubject(String trainingSubject) {
this.trainingSubject = trainingSubject;
}
public Set<UserProfile> getUserProfiles() {
return this.userProfiles;
}
public void setUserProfiles(Set<UserProfile> userProfiles) {
this.userProfiles = userProfiles;
}
}
UserProfile
#Entity
#Table(name = "user_profile", schema = "public")
public class UserProfile implements java.io.Serializable {
#Id #GeneratedValue
#Column(name = "profile_id", unique = true, nullable = false)
private Long profileId;
#Column(name = "profile_description")
private String profileDescription;
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
#JoinTable(name = "user_training", schema = "public", joinColumns = {
#JoinColumn(name = "profile_id", nullable = false, updatable = false) }, inverseJoinColumns = {
#JoinColumn(name = "training_id", nullable = false, updatable = false) })
private Set<Training> trainings = new HashSet<Training>(0);
public UserProfile() {
}
public String getProfileDescription() {
return this.profileDescription;
}
public void setProfileDescription(String profileDescription) {
this.profileDescription = profileDescription;
}
public Set<Training> getTrainings() {
return this.trainings;
}
public void setTrainings(Set<Training> trainings) {
this.trainings = trainings;
}
}
My json post via postman
And Response I get
Response show that new training record inserted in table having training_id as 67
No association found for this new saved training
again it created new record for training and does not associate with existing user profile , I post curl -i -X POST -H "Content-Type:application/json" -d "{ \"trainingSubject\" : \"Oracle\", \"userProfiles\":[\"/userProfiles/44\"] }" http://localhost:8080/api/trainings
You could use the relative url assignment:
{
"trainingSubject": "oracle",
"userProfiles":["/userProfiles/44"]
}
Maybe also try with the full url: http://localhost:8080/api/userProfiles/44
EDITED
If you move the owning site of the ManyToMany relation to Training it will work with the above JSON. So currently the owner is allowed to set the realtions. If you do it like that:
#ManyToMany
#JoinTable(name = "user_training"
, joinColumns = {#JoinColumn(name = "profile_id") }
, inverseJoinColumns = {#JoinColumn(name = "training_id") })
private List<UserProfile> userProfiles = new ArrayList<>();
plus
#ManyToMany(mappedBy = "userProfiles")
private List<Training> trainings = new ArrayList<>();
Training owns the relation within userProfiles.
I think in your case it's the best option for now. Another option would be, when keeping the owner site at UserProfile on transactions, to update the relation there like:
PATCH http://localhost:8080/api/userProfiles/44
{
"trainings": ["trainings/66", "trainings/67"]
}
But with this you would need multible rest calls (1. POST new training and get the new Id 2. GET current training list 3. PATCH trainings list with newly added training)
Last option would be to add the REST-controller on your own.
Complete files for the first approach:
#Entity
#Table
public class Training implements Serializable {
#Id
#GeneratedValue
private Long trainingId;
#ManyToMany
#JoinTable(name = "user_training"
, joinColumns = {#JoinColumn(name = "profile_id") }
, inverseJoinColumns = {#JoinColumn(name = "training_id") })
private List<UserProfile> userProfiles = new ArrayList<>();
#Column(name = "training_subject", length = 200)
private String trainingSubject;
#Entity
#Table
public class UserProfile implements Serializable {
#Id
#GeneratedValue
private Long profileId;
#Column(name = "profile_description")
private String profileDescription;
#ManyToMany(mappedBy = "userProfiles")
private List<Training> trainings = new ArrayList<>();
public interface TrainingRepository extends JpaRepository<Training, Long> {
}
public interface UserProfileRepository extends JpaRepository<UserProfile, Long> {
}
With the upper JSON this will work, I tested it. You will not see the correct result directly in the response of curl-POST. To see the added relation you must follow the userProfiles-link like GET http://localhost:8080/transactions/<newId>/userProfiles

Resources