Hibernate/Kotlin/Spring - ManyToMany cant delete items - spring

I have two tables that are linked with #ManyToMany .I want that when deleting items that it deletes only own items and not the connected ones. I have 2 Classes. In Class "A" there is no issue , it deletes only its own items and not the connected one (the ones that represent class B). But when i delete items from class "B" i am getting error that is not possible because of foreign key. I have tried to use CascadeType.REMOVE , which will work but it will also delete the connected items from class "A" which i don't want to.
Class A:
#Entity
#TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)
data class Student (
.....
#ManyToMany( fetch = FetchType.LAZY , cascade = [
CascadeType.PERSIST,
CascadeType.MERGE
])
#JoinTable(name = "student_teacher",
joinColumns = [JoinColumn(name = "student_id", referencedColumnName = "id")],
inverseJoinColumns = [JoinColumn(name = "teacher_id", referencedColumnName = "id")]
)
#JsonIgnore
val teachers: MutableSet<Teacher> ,
)
{
val teachersList: MutableSet<Teacher>
get() = teachers
}
Class B:
#Entity
#TypeDef(name = "list-array", typeClass = ListArrayType::class)
data class Teacher(
.....
){
#ManyToMany(mappedBy = "teachers",fetch = FetchType.LAZY)
#JsonIgnore
val students: MutableSet<Student> = MutableSet<Student>()
}

Got it work this way:
#Entity
#TypeDef(name = "list-array", typeClass = ListArrayType::class)
data class Teacher(
.....
){
#ManyToMany(mappedBy = "teachers",fetch = FetchType.LAZY)
#JsonIgnore
val students: MutableSet<Student> = MutableSet<Student>()
#PreRemove
fun removeTeacher() {
for (student in students) {
student.teachersList.remove(this)
}
}
}

Related

How does caching works for many to one mapping in hibernate

I have two entities station and company .
Station has many to one mapping with company. When Iam trying to fetch the list of stations based on company . It always hit the database. Below is my station entity class
#Table(name = "station")
#Setter
#Getter
#Builder
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Station {
public Station() {}
#Id
#SequenceGenerator(name = "mySeqGen", sequenceName = "station_seq",allocationSize = 1)
#GeneratedValue(generator = "mySeqGen")
#Column(name = "station_id ")
private Long id;
Double latitude;
Double longitude;
#ManyToOne(fetch = FetchType.LAZY , cascade = CascadeType.ALL)
#JoinColumn(name = "company_id" , unique =true)
#Fetch(value = FetchMode.JOIN)
Below is my code to find station list based on company
#Override
public List<StationDTO> findStations(final FindStationRequestDTO findStationRequestDTO) {
var company = companyRepository.findById(findStationRequestDTO.getRequestCompanyId());
var stationList = stationRepository.findByCompany(company);
return stationList.stream().map( station -> stationMapper.convertToStationDto(station , new StationDTO())).collect(Collectors.toList());
}
caching is working for companyRepository.findById(findStationRequestDTO.getRequestCompanyId());
public interface StationRepository extends JpaRepository<Station, Long> {
#QueryHints({
#QueryHint(name = HINT_CACHEABLE, value = "true")
})
List<Station> findByCompany(final Optional<Company> company);
}
adding QueryHints made it working

how to convert items from a javascript array to java objects spring boot

I want to post a subject and it has a promotorList and a targetAudienceList.
when i get the requestbody as a string i can print this on java side:
{
"title": "qsdgqsg",
"description": "qdfgqdg",
"amountOfStudents": "1",
"targetAudienceList": [
{
"targetAudience": {
"targetAudienceId": 1
}
},
{
"targetAudience": {
"targetAudienceId": 2
}
}
]
"promotorList": [
{
"promotor": {
"promotorId": 1
}
}
]
}
but when i take the requestbody as a subject object in java like this:
#PostMapping(path = "/create")
public void createSubject(#RequestBody Subject subject) {
System.out.println(subject);
//subjectService.addSubject(subject);
}
i get this if i print it:
Subject(subjectId=null, title=qsdgqsg, description=qdfgqdg, amountOfStudents=1, promotorList=[Promotor(promotorId=null, user=null, researchGroup=null)], topicList=null, targetAudienceList=[TargetAudience(targetAudienceId=null, majorCode=null, campus=null), TargetAudience(targetAudienceId=null, majorCode=null, campus=null)])
as you can see java makes entries for 2 targetaudiences in the list, but the id's are null for some reason.
Subject.java:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Subject {
#Id
#SequenceGenerator(
name = "subject_sequence",
sequenceName = "subject_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "subject_sequence"
)
private Long subjectId;
private String title;
private String description;
private int amountOfStudents;
#ManyToMany(
cascade = CascadeType.ALL
)
#JoinTable(
name = "subject_promotor",
joinColumns = #JoinColumn(
name = "subject_id",
referencedColumnName = "subjectId"
),
inverseJoinColumns = #JoinColumn(
name = "promotor_id",
referencedColumnName = "promotorId"
)
)
private List<Promotor> promotorList;
#ManyToMany(
cascade = CascadeType.ALL
)
#JoinTable(
name = "subject_topic",
joinColumns = #JoinColumn(
name = "subject_id",
referencedColumnName = "subjectId"
),
inverseJoinColumns = #JoinColumn(
name = "topic_id",
referencedColumnName = "topicId"
)
)
private List<Topic> topicList;
#ManyToMany(
cascade = CascadeType.ALL
)
#JoinTable(
name = "subject_targetAudience",
joinColumns = #JoinColumn(
name = "subject_id",
referencedColumnName = "subjectId"
),
inverseJoinColumns = #JoinColumn(
name = "targetAudience_id",
referencedColumnName = "targetAudienceId"
)
)
private List<TargetAudience> targetAudienceList;
TargetAudience.java:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class TargetAudience {
#Id
#SequenceGenerator(
name = "targetAudience_sequence",
sequenceName = "targetAudience_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "targetAudience_sequence"
)
private Long targetAudienceId;
// for example IW E-ICT-> industriele wetenschappen Elektronica ICT
private String majorCode;
#OneToOne(
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
optional = true
)
#JoinColumn(
name = "campus",
referencedColumnName = "name"
)
private Campus campus;
}
the solution might be obvious, but i'm new to spring boot and would appreciate an answer.
Thank's in advance!
Your request JSON is not matching your datamodel.
The correct JSON for the datamodel you have would be:
{
"title": "qsdgqsg",
"description": "qdfgqdg",
"amountOfStudents": 1,
"targetAudienceList": [
{
"targetAudienceId": 1
},
{
"targetAudienceId": 2
}
],
"promotorList": [
{
"promotorId": 1
}
]
}
When the objectMapper of Spring cant find a variable it will set it to null. Since your request is missing the values in the correct places spring identifies thos as missing. Additional data that is not needed in the restObject in your Controller is ignored.
I would also advise yout that you do not directly use Entitys in RestConttrollers. You should use DTOs --> https://softwareengineering.stackexchange.com/questions/171457/what-is-the-point-of-using-dto-data-transfer-objects.

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.

Hibernate saves child entity with null parent id

Hibernate doesn't want to save IDs for child entities. I have the following tables:
#Entity
#Table(name = "ct_orders")
data class Order(
#Id
#Column(name = "id")
#GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
val id: Int = 0,
#OneToMany(fetch = FetchType.LAZY, cascade = arrayOf(CascadeType.ALL), mappedBy = "order")
val route: List<Route>? = null,
...
)
#Entity
#Table(name = "ct_routes")
#JsonIgnoreProperties("id", "order")
data class Route(
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int = 0,
#Column
val location: Point = GeoHelpers.point(),
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
val order: Order? = null,
#Column
val title: String = ""
)
ct_routes saving with null in order_id. Is there some problem with relationships? Or, may be there is something wrong in my code?
Here is the part of code, which saves an Order entity:
val order = orderRepository.save(Order(
...
route = GeoHelpers.placesListToEntities(data.places),
...
))
fun placesListToEntities(points: List<PlaceDto>) = points.map {
Route(
location = Helpers.geometry(it.location.latitude, it.location.longitude),
title = it.title
)
}
You're modeling bidirectional #OneToMany and as shown in the example in the documentation you're responsible for setting the parent value on the child entity:
val order = orderRepository.save(Order(...).apply{
...
route = GeoHelpers.placesListToEntities(this, data.places),
...
})
fun placesListToEntities(order:Order, points: List<PlaceDto>) = points.map {
Route(
order = order,
location = Helpers.geometry(it.location.latitude, it.location.longitude),
title = it.title
)
}
PS. Since Route is an entity you could change your model a bit to enforce the constraints on the langauge level i.e:
class Route internal constructor() {
lateinit var order: Order
constructor(order: Order) : this() {
this.order = order
}
}
See this question for more details.

Resources