Spring Data Rest is showing a real puzzling behavior when updating an embedded association. In my example there is an entity Customer which has a _OneTo_Many_ relation to an entity Phones. The Phones are correctly shown as embedded array by a GET to the customers/50 entity-resource. But neither the PUT nor the PATCH show the expected results.
public class Customer {
public static final String TABLE_NAME = "CUSTOMER";
public static final String SEQUENCE_NAME = "S_CUSTOMER";
public static final String DISPLAY_NAME_COLUMN = "DISPLAY_NAME";
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "s_customer")
#SequenceGenerator(name = "s_customer", sequenceName = "S_CUSTOMER",
allocationSize = 50)
private Long id;
#NotEmpty
#Column(name = "DISPLAY_NAME")
private String displayName;
#OneToMany(mappedBy = "owner")
private List<Phone> phones;
#Version
private Long version;
}
public class Phone {
public static final String TABLE_NAME = "PHONE";
public static final String SEQUENCE_NAME = "S_PHONE";
public static final String OWNER_COLUMN = "OWNER";
public static final String PHONE_TYPE_COLUMN = "PHONE_TYPE";
public static final String NUMBER_COLUMN = "NUMBER";
enum PhoneType {
MOBILE, HOME, OFFICE
}
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "s_phone_number")
#SequenceGenerator(name = "s_phone_number", sequenceName = SEQUENCE_NAME, allocationSize = 50)
private Long id;
#ManyToOne
#JoinColumn(name = OWNER_COLUMN)
private Customer owner;
#Enumerated(EnumType.STRING)
#Column(name = PHONE_TYPE_COLUMN)
private PhoneType phoneType;
#NotEmpty
#Column(name = NUMBER_COLUMN)
private String number;
#Version
private Long version;
}
A POST to the entity /customer/50 produces a correct Update to the Customer, but the Insert to the Phone does not contain the foreign key to the customer:
[EL Fine]: sql: 2016-06-23
11:41:25.149--ClientSession(1317378011)--Connection(497905718)--Thread(Thread[http-nio-8081-exec-1,5,main])--UPDATE
CUSTOMER SET DISPLAY_NAME = ?, VERSION = ? WHERE ((ID = ?) AND
(VERSION = ?)) bind => [bla, 1, 50, 0] [EL Fine]: sql: 2016-06-23
11:41:25.15--ClientSession(1317378011)--Connection(497905718)--Thread(Thread[http-nio-8081-exec-1,5,main])--INSERT
INTO PHONE (ID, NUMBER, PHONE_TYPE, VERSION, OWNER) VALUES (?, ?, ?,
?, ?) bind => [1, 12345, MOBILE, 1, null]
This is the body of the PUT request:
{
"displayName": "bla",
"phones": [
{
"number": "12345",
"phoneType": "MOBILE"
}
]
}
So, Spring Data Rest correctly interprets the PUT as an update of the Customer and an insert into the Phone table, but just "forgets" about the relation. I assume this is a bug. Or is there something I forgot?
Btw: The PATCH behaves similar. There is again not foreign key in the phone record.
Edit:
The code of the CustomerRepository:
#Repository
public interface CustomerDao extends PagingAndSortingRepository<Customer, Long> {
List<Customer> findByDisplayName(#Param("name") String name);
}
Related
I have to entities modeled Session and Speaker, with ManyToMany relationship, and I wanted to delete an instance of Session, but in the DB it is the foreign key of another table. Below is the entity model
#Entity(name = "sessions")
public class Session {
// attributes do not respect camel case notations because they
// need to match table notations in order to auto bind without annotations
// otherwise that is done with #Column annotation
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long session_id;
private String session_name;
private String session_description;
private String session_length;
#OnDelete(action = OnDeleteAction.CASCADE)
#ManyToMany()
#JoinTable(
name = "session_speakers",
joinColumns = #JoinColumn(name = "session_id"),
inverseJoinColumns = #JoinColumn(name = "speaker_id")
)
private List<Speaker> speakers;
public Session() {
}
I tried to use OnDelete Cascade, but it still didn't work. (I did read that it is not advised to use on ManyToMany relationship)
#RequestMapping(value = "{id}", method = RequestMethod.DELETE)
public void delete(#PathVariable Long id){
sessionRepo.deleteById(id);
}
EDIT:
here is also the Speaker entity
#Entity(name = "speakers")
public class Speaker {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long speaker_id;
private String first_name;
private String last_name;
private String title;
private String company;
private String speaker_bio;
#Lob
#Type(type = "org.hibernate.type.BinaryType")
private Byte[] speaker_photo;
public Byte[] getSpeaker_photo() {
return speaker_photo;
}
public void setSpeaker_photo(Byte[] speaker_photo) {
this.speaker_photo = speaker_photo;
}
#ManyToMany(mappedBy = "speakers")
#JsonIgnore// added to resolve serialization issues
private List<Session> sessions;
I want to add the primary key from 2 tables (project and book mark) into a new table called ProjectBookmark which contains the primary key from my other two tables as foreign keys with the relationships shown below in springbok.
ERD Diagram
Below are my tables for Project and bookmark
Table 1
#Entity
public class Project {
#Id
#GeneratedValue
private long id;
#Column(name = "Project_Name", unique = true)
private String name;
#Column(name = "Description", unique = true)
private String description;
public Project(String name, String description) {
super();
this.name = name;
this.description = description;
}
Table 2
#Entity
public class Bookmark {
#Id
#GeneratedValue
private long id;
#Column(name = "Name", unique = true)
private String name;
#Column(name = "Type_of_resource", unique = true)
private String type;
#Column(name = "Description", unique = true)
private String description;
#Column(name = "URL", unique = true)
private String url;
public Bookmark(String name, String type, String description, String url) {
super();
this.name = name;
this.type = type;
this.description = description;
this.url = url;
}
Im not sure how to do the relationships and import in the primary keys as foreign keys to my table 3.
#Entity
public class ProjectBookmark {
}
If I understand your question correctly you need a ManyToMany relationship between Project and Bookmark.
You'll need to add the following code to your Project entity class.
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name="project_bookmark",
joinColumns = {#JoinColumn(name="project_id", referencedColumnName="id")},
inverseJoinColumns = {#JoinColumn(name="bookmark_id", referencedColumnName="id")}
)
private Set<Bookmark> bookmarks = new HashSet<>();
You don't need an additional id column in your ProjectBookmark table.
{ "id" :"3",
"userId": "abc",
"favName": "shashank",
"kpiName": "FavKPI",
"rptId": "529",
"language": "EN",
"selectedControlIdList": [
{
"favouriteId": 3,
"controlId": "3",
"controlName": " ",
"label": "Plant",
"keyValue": "KPI_01_PL_01_1",
"structureType": "LISTBOX"
},
{
"favouriteId": 3,
"controlId": "2",
"controlName": " ",
"label": "Plant12",
"keyValue": "KPI_01",
"structureType": "LISTBOX"
}
]
}
My controller class is
#RequestMapping(value = "/addFavData", method = RequestMethod.POST, consumes =MediaType.APPLICATION_JSON_VALUE, produces =MediaType.APPLICATION_JSON_VALUE)
public void addFavData(#RequestBody FavouriteDTO requestInputMapper) {
favouriteService.addFavouriteData(requestInputMapper);
}
service class
public void addFavouriteData(FavouriteDTO requestInputMapper)
{
favouriteRepository.save(requestInputMapper);
}
And these are entity class !!
#Entity
#Table(name = "favorite", schema = "quality")
public class FavouriteDTO implements Serializable{
private static final long serialVersionUID = -7089417397407966229L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "userId")
private String userId;
#Column(name = "favName")
private String favName;
#Column(name = "kpiName")
private String kpiName;
#Column(name = "rptId")
private String rptId;
#Column(name = "language")
private String language;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "favouriteId")
private List<DefaultControlsDTO> selectedControlIdList;
}
And
#Entity
#Table(name = "favoriteControls", schema = "quality")
public class DefaultControlsDTO implements Serializable {
private static final long serialVersionUID = 8720721227933753311L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "favouriteId")
private Integer favouriteId;
#Column(name = "controlId")
private String controlId;
#Column(name = "controlName")
private String controlName;
#Column(name = "label")
private String label;
#Column(name = "keyValue")
private String keyValue;
#Column(name = "structureType")
private String structureType;
}
here the id is auto genrated. and the favouriteId is same as id.
so how can i store the data as id is auto genrated and i need to put the same favourite id as in id. so how can i store the data in the data base
so i have given my entity class. i have two entity Favorite and DefaultFavuorite Entity.so how can i store the data
You can tell Hibernate, and any other JPA implementation, to cascade certain operations you perform on an entity to its associated child entities. The only thing you have to do is to define the kind of operation you want to cascade to the child entities.
The following code snippet shows an example in which I cascade the persist operation of the Author entity to all associated Book entities.
#Entity
public class Author {
…
#ManyToMany(mappedBy=”authors”, cascade = CascadeType.PERSIST)
private List<Book> books = new ArrayList<Book>();
…
}
When you now create a new Author and several associated Book entities, you just have to persist the Author entity.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Author a = new Author();
a.setFirstName(“John”);
a.setLastName(“Doe”);
Book b1 = new Book();
b1.setTitle(“John’s first book”);
a.getBooks().add(b1);
Book b2 = new Book();
b2.setTitle(“John’s second book”);
a.getBooks().add(b2);
em.persist(a);
em.getTransaction().commit();
em.close();
As you can see in the log output, Hibernate cascades the operation to the associated Book entities and persists them as well.
15:44:28,140 DEBUG [org.hibernate.SQL] – insert into Author (firstName, lastName, version, id) values (?, ?, ?, ?)
15:44:28,147 DEBUG [org.hibernate.SQL] – insert into Book (publisherid, publishingDate, title, version, id) values (?, ?, ?, ?, ?)
15:44:28,150 DEBUG [org.hibernate.SQL] – insert into Book (publisherid, publishingDate, title, version, id) values (?, ?, ?, ?, ?)
I would like to link a comment table and a film table with a user table. I wish to allow a user to have many comments, and a film have many comments. I then want to display a list of comments in a details page for each film, giving the option for the user who created the comment to delete or update it.
I altered my code in an attempt to create a one to many relation between comment and film, but I get the error:
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column
"FILM_ID"; SQL statement: alter table film add column film_id bigint
not null [23502-196]
It makes me think two things:
1) Set to allow null or figure out why there is a null field. I attempted allow null by adding #Column(name = "film_id", nullable = true) but it said parameter is redundant.
2) Film table has auto incrementing ID already, so by adding #Column(name = "film_id") am I duplicating an ID? As with the error message saying "add column" it made me think so?
My attempt currently stands at:
Film.java
package com.demo.spring.domain;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
#Entity
public class Film {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "film_id", nullable = true)
Long id;
String title;
String director;
String description;
#DateTimeFormat(pattern="yyyy-MM-dd")
Date date;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "film_id", referencedColumnName = "film_id")
List<Comment> comments;
public List<Comment> getComments() {
return comments;
}
public void setComments(List<Comment> comments) {
this.comments = comments;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
//rest of getter and setters below//
Comment.java
package com.demo.spring.domain;
import javax.persistence.*;
#Entity
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "comment_id")
Long id;
String body;
#Column(name = "film_id")
Long filmId;
public Long getFilmId() {
return filmId;
}
public void setFilmId(Long filmId) {
this.filmId = filmId;
}
public Comment(){
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
UPDATE
I have changed Film.java..
From:
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "film_id", referencedColumnName = "film_id")
List<Comment> comments;
To:
#OneToMany(cascade = CascadeType.ALL)
List<Comment> comments;
And if I add in Comment.java:
#OneToMany(cascade=CascadeType.ALL)
#JoinTable(name="film", joinColumns=#JoinColumn(name = "film_id_fk", referencedColumnName = "film_id"))
private Set<Comment> comment = new HashSet<Comment>();
Film film;
I get:
MappingException: Foreign key
(FK5vk85sy54a8be115ye9ra1lyu:film_comments [film_film_id])) must have
same number of columns as the referenced primary key (film
[film_id_fk,comment_comment_id])
If I change private Set<Comment> comment = new HashSet<Comment>(); to List<Comment> comments = new ArrayList<Comment>(); I get:
NULL not allowed for column "FILM_ID"; SQL statement: alter table film
add column film_id bigint not null
And if instead I add:
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name = "film_id_fk", referencedColumnName = "film_id")
private Set<Comment> comment = new HashSet<Comment>();
Film film;
I get:
MappingException: Could not determine type for:
com.demo.spring.domain.Film, at table: comment, for columns:
[org.hibernate.mapping.Column(film)]
If I change private Set<Comment> comment = new HashSet<Comment>(); to List<Comment> comments = new ArrayList<Comment>(); I get:
NULL not allowed for column "FILM_ID"; SQL statement: alter table film
add column film_id bigint not null
A primary key can't be null, so you can't make "film_id" nullable. And your #JoinColumn annotation is wrong, that goes on the #ManyToOne side. The name parameter should be the name of the foreign key column in the Comments table (so it can't be the same name as the primary key) and referencedColumnName should be the name of the column that you're referencing in the other table
#Entity
public class Film {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "film_id")
Long id;
String title;
String director;
String description;
#DateTimeFormat(pattern="yyyy-MM-dd")
Date date;
#OneToMany(cascade = CascadeType.ALL)
List<Comment> comments;
//...
}
#Entity
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "comment_id")
Long id;
String body;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "film_id_fk", referencedColumnName = "film_id")
Film film;
//...
}
I Have below Entities :
#Entity(name = "USRGRP_MAP")
public class UserGroupMapping {
#Id
#Column(name = "USRGRP_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_GRP_MAP_SEQ")
#SequenceGenerator(sequenceName = "usrgrp_map_seq",allocationSize = 1,name = "USER_GRP_MAP_SEQ")
private Long mappingId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "USR_GRP_ID", referencedColumnName = "USR_GRP_ID")
private UserGroup group;
#Column(name = "USR_USRGRP_ACT")
private String userGroupAct;
getter/setters
}
#Entity(name = "USER")
public class User {
#Id
#Column(name = "USER_ID")
private Long userId;
#Column(name = "LOGIN_ID")
private String userName;
getter/setters
}
#Entity(name = "USR_GRP")
public class UserGroup {
#Id
#Column(name = "USR_GRP_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_GRP_SEQ")
#SequenceGenerator(sequenceName = "usr_grp_seq",allocationSize = 1,name = "USER_GRP_SEQ")
private long groupId;
#Column(name = "GRP_NM")
private String groupName;
#Column(name = "GRP_DESC")
private String groupDesc;
getter/setters
}
UserGroupMapping contains has many to one relationship with both user and group.
Now I want to do CRUD operation on UserGroupMapping for that I have created repository as below:
public interface UserGroupMappingRepository extends JpaRepository<UserGroupMapping, Long> {
List<UserGroupMapping> findByGroup(UserGroup group);
List<UserGroupMapping> findByUser(User user);
}
Now I want to write delete operation(for particular user and group) on UserGroupMapping without deleting any entry in USER and USR_GRP table , Just need to remove entry from USRGRP_MAP table.
I am trying to achieve it using native query:
#Query(value = "delete from USR_USRGRP_MAP where user_id = :userId and usr_grp_id = :groupId",nativeQuery = true)
void deleteUserGroupMappingByUserAndGroup(#Param("userId") Long userId, #Param("groupId") Long groupId);
Facing Exception Invalid SQL grammar, although query work fine in sql developer.
Below is my service class:
#Service
public class UserGroupMappingServiceImpl implements UserGroupMappingService{
#Autowired
private UserGroupMappingRepository repository;
#Override
public void deleteUserGroupMapping(Long userId, Long groupId) {
repository.deleteUserGroupMappingByUserAndGroup(userId,groupId);
}
}
Could anyone suggest correct way to delete entry from UserGroupMapping without deleting user and group ?
Below is USRGRP_MAP table:
USRGRP_ID USER_ID USR_USRGRP_ID USR_USRGRP_ACT
------------- ---------- ------------- -
41 306106 41 Y
14 108527 14 Y
8 295597 8 N
10 296518 10 Y
11 295597 11 Y
Thanks in advance .
Try to change
#Query(value = "delete from USR_USRGRP_MAP where user_id = :userId and usr_grp_id = :groupId",nativeQuery = true)
void deleteUserGroupMappingByUserAndGroup(#Param("userId") Long userId, #Param("groupId") Long groupId);
To this:
#Modifying
#Query(value = "delete from USR_USRGRP_MAP where user_id = :userId and usr_grp_id = :groupId",nativeQuery = true)
void deleteUserGroupMappingByUserAndGroup(#Param("userId") Long userId, #Param("groupId") Long groupId);
Cheers
~Emil