Deleting Entities #OneToMany #ManyToOne. Key is still referenced from table - java

I have two entities - User and Song:
User.class:
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "login",unique = true)
private String login;
#Column(name = "password")
private String password;
#Column(name = "token")
private UUID token;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(
name = "users_songs",
joinColumns = #JoinColumn(
name = "u_id",
referencedColumnName = "user_id"
),
inverseJoinColumns = #JoinColumn(
name = "s_id",
referencedColumnName = "song_id"
)
)
private List<Song> songs = new ArrayList<>();
}
Song.class:
#Entity
#Table(name = "songs")
public class Song {
#Id
#Column(name = "song_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "song_title")
private String songTitle;
#Column(name = "composer_name")
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "composers")
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "composer_id")
private Set<String> composer;
#Column(name = "author_of_words_name")
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "author_of_words")
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "authorOfWords_id")
private Set<String> authorOfWords;
#Column(name = "song_artist")
private String songArtist;
#Column(name = "song_timing")
private int songTiming;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
#JoinColumn(name = "user_id_key")
private User user;
#Column(name = "rate_value")
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "rating")
#MapKeyColumn(name = "login")
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn
private final Map<String, Integer> rating = new HashMap<>();
#Column
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "comment")
#MapKeyColumn(name = "login")
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn
private final Map<String, String> comments = new HashMap<>();
}
I want to delete a Song, but not delete a User.
I tried deleting it like this:
Query query = session.createQuery("DELETE Song s WHERE s.user = :user AND s.songTitle = :songTitle");
query.setParameter("user", user);
query.setParameter("songTitle", songTitle);
query.executeUpdate()
But the error drops:
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement...
Caused by: org.postgresql.util.PSQLException: ERROR: update or delete on table "songs" violates foreign key constraint "fka7me64vk6jtx81wt2ggbvm6ur" on table "users_songs"
Key (song_id)=(28) is still referenced from table "users_songs".
I understand that the problem is due to the fact that in the spanning table users_songs, which occurred due to the connection #OneToMany.
I read a similar question in the STO and it says that you need to break the connection. how to delete the record from the join table in hibernate
I executed the following code:
List<Song> collect = user.getSongs().stream().filter(s -> !s.getSongTitle().equals(songTitle)).collect(Collectors.toList());
user.setSongs(collect);
session.update(user);
Query query = session.createQuery("DELETE Song s WHERE s.songTitle = :songTitle");
query.setParameter("songTitle", songTitle);
query.executeUpdate();
This code works, but I don't think it's quite right. Now the question. How do I delete a Song from a user? With the condition that the song should not cascade to delete the user.
Please help me solve this issue correctly. I'd really appreciate it.

You can specify ON DELETE CASCADE for the foreign key constraint fka7me64vk6jtx81wt2ggbvm6ur on the table users_songs like below:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(
name = "users_songs",
joinColumns = #JoinColumn(
name = "u_id",
referencedColumnName = "user_id"
),
inverseJoinColumns = #JoinColumn(
name = "s_id",
referencedColumnName = "song_id"
),
inverseForeignKey = #ForeignKey(
name = "users_songs_songs_FK", // fka7me64vk6jtx81wt2ggbvm6ur renamed to users_songs_songs_FK
foreignKeyDefinition = "FOREIGN KEY (s_id) REFERENCES songs(song_id) ON DELETE CASCADE"
)
)
private List<Song> songs = new ArrayList<>();
and after database schema regeneration your initial query should work as expected.

Related

Query for tables with #ManyToMany relation(Hibernate)

I rewrite my SpringMVC app with using Hibernate.I try to make query for selecting lectures for group of students by id of group.with using SQL query(before I started rewrite it with using Hibernate)this query was:
"SELECT * FROM lectures WHERE id IN (SELECT lecture_id FROM lectures_groups WHERE group_id =?) ORDER BY date_of_lecture"
I have Lecture and Group etities:
#Entity
#Table(name = "lectures")
public class Lecture {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "lectures_groups", joinColumns = #JoinColumn(name = "lecture_id"), inverseJoinColumns = #JoinColumn(name = "group_id"))
private List<Group> groups = new ArrayList<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "teacher_id")
private Teacher teacher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "subject_id")
private Subject subject;
#Column(name = "date_of_lecture")
private LocalDateTime date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "audience")
private Audience audience;
public Lecture() {
}
//getters setters
}
and:
#Entity
#Table(name = "groups")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "group_name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cathedra_id", referencedColumnName = "id")
private Cathedra cathedra;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "lectures_groups", joinColumns = #JoinColumn(name = "group_id"), inverseJoinColumns = #JoinColumn(name = "lecture_id"))
private List<Lecture> lectures = new ArrayList<>();
public Group() {
}
//getters setters
}
I tried somthing like:
List<Lecture> lectures = session.createQuery("select l from lectures l join l.groups g where g.id=:groupId")
.setParameter("groupId", group.getId())
.list();
but I get Exception: org.hibernate.hql.internal.ast.QuerySyntaxException: lectures is not mapped
So how can i do it?
In hql query you need to provide the name of the entity in the query instead of the table name. So in your case, you should replace lectures with Lecture in the query.
List<Lecture> lectures = session.createQuery("select l from Lecture l join l.groups g where g.id=:groupId")
.setParameter("groupId", group.getId())
.list();

Get records from two table in relation many to many

I have two table in relation many to many
public class Repertoire {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, unique = true)
private Integer id;
private String name;
private Integer dayWeek;
#ManyToMany(cascade = CascadeType.REMOVE)
#JoinTable(
name = "repertoire_seance",
joinColumns = { #JoinColumn(name = "repertoire_id")},
inverseJoinColumns = {#JoinColumn(name = "seance_id")}
)
List<Seance> seances = new ArrayList<>();
}
and
public class Seance {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, unique = true)
private Integer id;
private java.time.LocalTime displayTime;
#ManyToOne
private Film film;
#Column(length=127)
private String kind;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private Hall hall;
#OneToMany(mappedBy = "reservationSeance")
#JsonIgnore
private List<Reservation> reservations = new ArrayList<>();
}
Hibernate create linked tabel repertoire_seance is posible get seances by seancesId and repertoire.dayWeek using spring data (JpaRepository) something like that findBySeanceIdAndRepertoireDayWeek()?
You could achieve this by writing an HQL.
It would look something like this:
select s from Repertoire r inner join r.seances s where r.dayWeek ="Your Value" and s.id = "Your Id Value"

hibernate table with 3 foreign keys

I'm trying to map the following table to an entity.
The mapping for the event table looks like this:
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer _id;
#Column(name = "title")
private String _title;
#Column(name = "description")
private String _description;
#Column(name = "location")
private String _place;
#Column(name = "start")
private Date _start;
#Column(name = "end")
private Date _stop;
#Column(name = "points")
private int _points;
#Enumerated(EnumType.STRING)
#Column(name = "type")
private EventType _eventtype;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "event2work2instrumentation", joinColumns = {
#JoinColumn(name = "event")},
inverseJoinColumns = {#JoinColumn(name = "work")})
private Set<WorkMapper> _works = new HashSet<>(0);
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "event2work2instrumentation", joinColumns = {
#JoinColumn(name = "event")},
inverseJoinColumns = {#JoinColumn(name = "instrumentation")})
private Set<InstrumentationMapper> _instrumentations = new HashSet<>(0);
It works fine and i get all the data from the database, but when I try to store a new event which has a work and instrumentation assigned to it I get errors.
When the fields in the event2work2instrumentation table are configured to not be null, i get: field does not have a default value.
If I give the field default values I get:
SQLIntegrityConstraintViolationException: Cannot add or update a child row:
a foreign key constraint fails (`schema`.`event2work2instrumentation`, CONSTRAINT `e2w2i_instrumentation_fk` FOREIGN KEY (`instrumentation`) REFERENCES `instrumentation` (`id`))
You have any ideas? May it depend on the Mappings of instrumentation and/or Work? These classes do not have a reference to event2work2instrumentation in their mapping.
Try truncating both tables and then store the new FK. It happened to me when I had a object_related_id which wasn't in object table as an id
The solution is to use a map and the MapKeyJoinColumn annotation:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "event2work2instrumentation", joinColumns = {
#JoinColumn(name = "event")},
inverseJoinColumns = {#JoinColumn(name = "instrumentation")})
#MapKeyJoinColumn(name = "work")
private Map<WorkMapper, InstrumentationMapper> _eventToWorkAndInstrumentationMappers = new HashMap<>();

Foreign key must have same number of columns as the referenced primary key. But I'm not using a composite key

I have a LibraryModel class, a LibraryImage class and a LibraryAttribute class. A LibraryModel can have an arbitrary number of LibraryImages and LibraryAttributes.
The error that I get:
org.hibernate.MappingException: Foreign key (FKmbn4xh7xdxv371ao5verqueu3:library_item_attribute [LIBRARY_ITEM_ATTRIBUTE_ID])) must have same number of columns as the referenced primary key (library_item_attribute [LIBRARY_ITEM_ID,LIBRARY_ITEM_ATTRIBUTE_ID])
Here are my annotated Objects:
Library Model:
#Entity
#Table(name = "library_item", uniqueConstraints = {
})
#Inheritance(strategy = InheritanceType.JOINED)
public class LibraryItemModel implements LibraryItem{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_ITEM_ID", unique = true, nullable = false)
private Integer libraryItemId;
#Column(name = "ITEM_TITLE", unique = false, nullable = false)
private String itemTitle;
#Column(name = "IS_PARENT", nullable = false)
private Boolean isParent;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ID", nullable = false)
private LibraryModel libraryModel;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "ITEM_LISTING",
joinColumns = {#JoinColumn(name = "PARENT_LIB_ITEM_ID", nullable=false)},
inverseJoinColumns = {#JoinColumn(name="CHILD_LIB_ITEM_ID", nullable = false)})
private Set<LibraryItemModel> itemChildren = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "itemChildren")
private Set<LibraryItemModel> itemParents = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "LIBRARY_ITEM_IMAGE",
joinColumns = { #JoinColumn(name = "LIBRARY_ITEM_ID", nullable=false)},
inverseJoinColumns = { #JoinColumn(name="LIBRARY_IMAGE_ID", nullable = false)})
private Set<LibraryImage> itemImages = new HashSet<>();
#OneToMany(fetch = FetchType.LAZY, mappedBy = "rootLibraryItemModel")
private Set<LibraryModel> libraries = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "LIBRARY_ITEM_ATTRIBUTE",
joinColumns = { #JoinColumn(name = "LIBRARY_ITEM_ID", nullable =false)},
inverseJoinColumns = { #JoinColumn(name="LIBRARY_ITEM_ATTRIBUTE_ID", nullable = false)})
private Set<LibraryItemAttribute> libraryItemAttributes = new HashSet<>();
}
LibraryImage:
#Entity
#Table(name = "library_image", uniqueConstraints = {
})
public class LibraryImage {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_IMAGE_ID", unique = true, nullable = false)
private Integer libraryImageId;
#Column(name = "IMAGE_LOCATION")
private String imageLocation;
#Column(name = "IMAGE_TITLE")
private String imageTitle;
#Enumerated(EnumType.STRING)
private LibraryImageType imageType;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ITEM_ID", nullable = false)
private LibraryItemModel libraryItemModel;
}
Library Attribute:
#Entity
#Table(name = "library_item_attribute", uniqueConstraints = {
})
public class LibraryItemAttribute {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_ITEM_ATTRIBUTE_ID", unique = true, nullable = false)
private Integer libraryItemAttributeId;
#Column(name = "ATTRIBUTE_NAME")
private String attributeName;
#Column(name = "ATTRIBUTE_VALUE")
private String attributeValue;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ITEM_ID", nullable = false)
private LibraryItemModel libraryItemModel;
}
This is really frustrating as the LibraryImage class is mapped without problems and doesn't throw any errors, but the LibraryAttribute class which is annotated in an identical way to the LibraryImage class is throwing this error.
Can someone please have a look and let me know what my problem is?
Found the problem.
In the LibraryItemModel class I defined the Join table with the LibraryItemAttribute to be called "LIBRARY_ITEM_ATTRIBUTE", which is the name of the table of the Library item attributes.
The join table is a different table and should have a different table name.
For the Library Image table above, the image table is called library_image, while the join table is called LIBRARY_ITEM_IMAGE

Can't write my SQL query in Spring Data JPA custom repository

There is part of SQL i want to realize in my Custom JPA repository
SELECT * FROM users u
JOIN skills_user sku on sku.user_id = u.id
JOIN specs_user spu on spu.user_id = u.id
GROUP BY u.id
HAVING ANY(sku.dictionary_id in (15,20) or spu.dictionary_id in (15,20))
ORDER BY u.id
I tried this:
//Other predicates
if (filterQuery.getSkills() != null && !filterQuery.getSkills().isEmpty()) {
String[] tmp = filterQuery.getSkills().replaceAll(" ", "").split(",");
List<Integer> ids = new ArrayList<>();
for (String s : tmp) {
ids.add(Integer.parseInt(s));
}
List<Predicate> tmpPredicates = new ArrayList<>();
Join<User, Dictionary> skillJoin = root.join("skillList");
Join<User, Dictionary> specsJoin = root.join("specsList");
for (Integer id : ids) {
tmpPredicates.add(builder.or(builder.equal(skillJoin.get("id"), id), builder.equal(specsJoin.get("id"), id)));
}
predicates.add(builder.and(tmpPredicates.toArray(new Predicate[tmpPredicates.size()])));
}
//Other predicates
But it isn't work correctly.
How can i realise this correctly in JPA custom repository?
there is code of User and Dictionary classes:
#Entity
#SequenceGenerator(name = "user_gen", sequenceName = "users_seq")
#Table(name = "users")
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_gen")
private Long id;
#Column(name = "login")
private String login;
#Column(name = "password")
private String password;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#Column(name = "middlename")
private String middlename;
#Column(name = "academic_group")
private String academicGroup;
#Column(name = "entrance_year")
private int entranceYear;
#Column(name = "avatar_URL")
private String avatarURL;
#Column(name = "salt")
private String salt;
#Enumerated(EnumType.ORDINAL)
#Column(name = "user_group")
private UserGroup group;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "SocialRole_User", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "socialRole_id",
nullable = false, updatable = false) })
private List<SocialRole> socialRoleList;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "specs_user", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = true)},
inverseJoinColumns = {#JoinColumn(name = "dictionary_id",
nullable = false, updatable = true)})
private List<Dictionary> specsList;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "skills_user", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = true)},
inverseJoinColumns = {#JoinColumn(name = "dictionary_id",
nullable = false, updatable = true)})
private List<Dictionary> skillList;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Contacts> contactsList;
//Getters and setters
}
Dictionary:
#Entity
#SequenceGenerator(name = "dictionary_gen", sequenceName = "dictionary_seq")
#Table(name = "dictionary")
public class Dictionary {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "dictionary_gen")
private Long id;
#Column(name = "dic_name")
private String name;
#Enumerated(EnumType.STRING)
#Column(name = "dic_type")
private DictionaryType type;
// Getters and Setters
}
Have you tried writing the query using JPQL?
SELECT a FROM User a
INNER JOIN a.specsList b
INNER JOIN a.skillList c
GROUP BY a.id
HAVING ANY(b.id in (15,20) OR c.id in (15,20))
ORDER BY a.id;
This JPQL should work the same as your plain SQL.

Categories

Resources