I wrote a few service methods. When I implement CommandLineRunner to my SpringApplication and try to execute service methods in run methods I can see they're working.
I mean like this approach, I can reach the data via service methods. So I can say yes my services are working right.
public void run(String... args) throws Exception {
postService.getAllPost()
.stream()
.forEach(entry -> System.out.println(entry.getTitle()));
}
But When I try to use my controllers via postman. For the User class, everything is fine.
public List<UserDto> getAllUsers() {
return userRepository.findAll()
.stream()
.map(userMapper::toDto)
.collect(toList());
}
#GetMapping("/get")
public ResponseEntity<List<UserDto>> getAll() {
return ResponseEntity.ok(userService.getAllUsers());
}
But Comment and Post are not working.
The methods are almost same. I was wondering is it about my entities relation? Because in User entity I just have 3 different field which is Long, String , String . But the other entities are related with each others. With OneToMany relational. And I dont trust about my business plan. I mean post class includes user and comment object. Comment class includes user object etc.
This is controller method.
#GetMapping("/getAll")
public ResponseEntity<List<CommentDto>> fetchAll() {
return ResponseEntity.ok(commentService.getAllComments());
}
This is service method.
public List<CommentDto> getAllComments() {
return commentRepository.findAll().stream()
.map(commentMapper::toDto)
.collect(Collectors.toList());
}
I have three different entity class.
Which is
#Entity
#Table(name = "user_table")
public class User{
#Id
private Long id;
private String userName;
private String password;
}
#Entity
#Table(name = "post_table")
public class Post {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id" , nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
private String title;
#Lob
#Column(columnDefinition = "text")
private String text;
}
#Entity
#Table(name = "comment_table")
public class Comment {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "post_id" , nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Post post;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id" , nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
#Lob
#Column(columnDefinition = "text")
private String text;
Related
In my Spring Boot app, I use Hibernate and applied the necessary relations to the following entities properly.
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false, length=50)
private String title;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
private List<RecipeIngredient> recipeIngredients = new ArrayList<>();
}
#Entity
public class RecipeIngredient {
#EmbeddedId
private RecipeIngredientId recipeIngredientId = new RecipeIngredientId();
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#MapsId("recipeId")
#JoinColumn(name = "recipe_id", referencedColumnName = "id")
private Recipe recipe;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#MapsId("ingredientId")
#JoinColumn(name = "ingredient_id", referencedColumnName = "id")
private Ingredient ingredient;
}
#Entity
public class Ingredient
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique=true, nullable=false, length=50)
#EqualsAndHashCode.Include
private String name;
#OneToMany(mappedBy = "ingredient", cascade = CascadeType.ALL)
private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
}
Now I am trying to retrieve data by merging related entities. For example, when retrieving a Recipe, I also need to retrieve all Ingredients belonging to this Recipe.
As far as I know, I can use Projection and maybe it is better to only use Hibernate features and retrieve related table data via Java Stream. I have no idea how should I retrieve data via Hibernate.
Suppose that I just need an Optional<Recipe> that has List<Ingredient>. Then, I probably need a DTO class something like that:
#Data
public class ResponseDTO {
private Long id;
private String title;
List<RecipeIngredient> ingredients;
// getter, setter, constructor
}
So, how should I populate this DTO with the requested Recipe and corresponding Ingredient data (getting Ingredient names besides id values) using Java Stream?
Or if you suggest Projection way, I tried it but the data is multiplied by the ingredient count belonging to the searched recipe.
Update:
#Getter
#Setter
#NoArgsConstructor
public class ResponseDTO {
private Long id;
private String title;
List<IngredientDTO> ingredientDTOList;
public ResponseDTO(Recipe recipe) {
this.id = recipe.getId();
this.title = recipe.getTitle();
this.ingredientDTOList = recipe.getRecipeIngredients().stream()
.map(ri -> new IngredientDTO(ri.getIngredient().getName()))
.toList();
}
}
#Getter
#Setter
public class IngredientDTO {
private Long id;
private String name;
public IngredientDTO(String name) {
this.name = name;
}
}
First, in the ResponseDTO you will need you change the type of ingredients from List<RecipeIngredient> to List<Ingredient>.
To manually perform the mapping, you should use (to map from a suppose Recipe recipe to a RespondeDTO response):
ResponseDTO recipeToResponseDTO(Recipe recipe) {
ResponseDTO response = new ResponseDTO();
response.setId(recipe.getId());
response.setTitle(recipe.getTitle());
response.setIngredients(recipe.recipeIngredients.stream()
.map(RecipeIngredient::getIngredient()
.collect(Collectors.toList());
return response;
}
On the other hand, to model a n-n relation, I encourage you to use the approach proposed by E-Riz in the comment.
I am currently playing around with SpringBoot and wanna create a little API that allows me to save and fetch persistent data. I can't find the right solutions online, thats why I asking here. Creating an entity and with that a database table was very easy to do, and so was the implementation of the POST and GET request.
I have a very basic idea here. I have a table of players. Each of those players can participate in a foosball game, taking one of the four possible positions.
One player can have multiple games. A game can have one player (For each field).
Because of how easy everything was till the entity relation, I would assume that SpringBoot can automatically fetch the right player based on the id, that is inside of the POST request. But at the moment my application just throws an error, because my players are null and I made them non-nullable.
Do I need to manually fetch the player from the PlayerRepository and append them on the game object or do I miss some annotations? What would be the best practice to pull of those four API calls?
That how I would design my POST request:
{
"attackBlackPlayerId": 1,
"attackYellowPlayerId": 2,
"defenseBlackPlayerId": 3,
"defenseYellowPlayerId": 4,
"black_won": true
}
#Entity
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
private String email;
#CreationTimestamp
#Column(nullable = false)
private LocalDateTime creationDate;
}
#Entity
public class Game {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#CreationTimestamp
#Column(nullable = false)
private LocalDateTime playDateTime;
#ManyToOne(optional = false)
#JoinColumn(nullable = false)
private Player attackBlackPlayer;
#ManyToOne(optional = false)
#JoinColumn(nullable = false)
private Player defenseBlackPlayer;
#ManyToOne(optional = false)
#JoinColumn(nullable = false)
private Player attackYellowPlayer;
#ManyToOne(optional = false)
#JoinColumn(nullable = false)
private Player defenseYellowPlayer;
#Column(nullable = false)
private boolean blackWon;
}
#RestController
public class API {
#Autowired
private PlayerRepository playerRepository;
#Autowired
private GameRepository gameRepository;
#GetMapping("/players")
public #ResponseBody Iterable<Player> getPlayers() {
return playerRepository.findAll();
}
#PostMapping("/player")
public #ResponseBody Player addPlayer(#RequestBody Player player) {
return playerRepository.save(player);
}
#GetMapping("/games")
public #ResponseBody Iterable<Game> getGames() {
return gameRepository.findAll();
}
#PostMapping("/game")
public #ResponseBody Game addGame(#RequestBody Game game) {
return gameRepository.save(game);
}
}
Your Lord Tkay
The #OneToMany and #ManyToOne annotations have fields which must be correctly initialized if you want that the mapping works as expected.
Example :
#Entity
public class Employee {
#Id
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "employee")
private List<Email> emails;
}
#Entity
public class Email {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "employee_id")
private Employee employee;
}
Problem I'm trying to solve
I'm trying to model a #ManyToMany relation between a User and Role, such that a user can have n roles, and one role is referenced by several users. A role can be persisted even if it's not referenced by any user (detached), and a user with no roles is allowed too.
The same kind of relation must be built between Role and ResourcePermission.
To give you an idea about how each entity looks like:
Both ResourcePermission and Role have a finite set of values. For example, if Patient happens to be a resource, then one resource permission could be "PATIENT:READ" or "PATIENT:WRITE", and the role DOCTOR has several of these permissions. I hope it's clear sofar how my data model looks like.
What I'm using
Currently, I'm using spring-data-jpa version 2.4.2 to model my entities, and to create my CRUD repos. Except for base path and media type, I don't have any specific configuration (all is set to default).
Hibernate is my persistence provider atm .
Concerning my datasource, I'm using in-memory H2 for my development environment, and again no specific configuration there either.
How I'm solving it
Here's how my entities look like
User.java
#Table
#Entity
#Data
public class User implements Serializable {
private static final long serialVersionUID = 1123146940559321847L;
#Id
#GeneratedValue(generator = "user-id-generator")
#GenericGenerator(name = "user-id-generator",
strategy = "....security.entity.UserIdGenerator",
parameters = #Parameter(name = "prefix", value = "USER-")
)
#Column(unique = true, nullable = false)
private String id;
#Column
private int age;
#Column(unique = true, nullable = false)
private String username;
#Column(unique = false, nullable = false)
private String password;
#ManyToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.MERGE
)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private List<Role> roles = Collections.emptyList();
public User withId(final String id) {
this.id = id;
return this;
}
public User withAge(final int age) {
this.age = age;
return this;
}
public User withUsername(final String username) {
this.username = username;
return this;
}
public User withPassword(final String password) {
this.password = password;
return this;
}
public User withRoles(final Role... roles) {
return withRoles(Arrays.stream(roles).collect(Collectors.toList()));
}
public User withRoles(final List<Role> roles) {
this.roles = roles;
return this;
}
}
Role.java
#Data
#NoArgsConstructor
#Table
#Entity
public class Role implements Serializable {
private static final long serialVersionUID = 812344454009121807L;
#Id
private String roleName;
#ManyToMany(
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
#JoinTable(
name = "role_resource_permission",
joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "resource_permission_id")
)
private Set<ResourcePermission> resourcePermissions = Collections.emptySet();
#ManyToMany(
mappedBy = "roles",
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
private List<User> users = Collections.emptyList();
public Role(final String roleName) {
setRoleName(roleName);
}
public void setRoleName(final String roleName) {
final RoleType roleType = RoleType.of(roleName);
this.roleName = roleType.getRoleName();
final Set<ResourcePermission> resourcePermissions = roleType.getResourcePermissions().stream()
.map(ResourcePermissionType::getPermissionName)
.map(ResourcePermission::new)
.collect(Collectors.toSet());
setResourcePermissions(resourcePermissions);
}
public void setResourcePermissions(final Set<ResourcePermission> resourcePermissions) {
if (this.resourcePermissions.isEmpty()) {
this.resourcePermissions = resourcePermissions;
}
}
}
ResourcePermission.java
#NoArgsConstructor
#Data
#Table
#Entity
public class ResourcePermission implements Serializable {
private static final long serialVersionUID = 883231454000721867L;
#Id
private String permissionName;
public ResourcePermission(final String permissionName) {
setPermissionName(permissionName);
}
#ManyToMany(
mappedBy = "resourcePermissions",
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
private Set<Role> roles = Collections.emptySet();
public void setPermissionName(String permissionName) {
final ResourcePermissionType permissionType = ResourcePermissionType.of(permissionName);
this.permissionName = permissionType.getPermissionName();
}
}
RoleType.java
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum RoleType {
DOCTOR("DOCTOR", doctorsPermissions()),
TECHNICIAN("TECHNICIAN", technicianPermission()),
ADMIN("ADMIN", adminPermissions());
#Getter
private String roleName;
#Getter
private final List<ResourcePermissionType> resourcePermissions;
public static RoleType of(final String roleName) {
return Arrays.stream(values())
.filter(roleType -> roleType.getRoleName().equals(roleName.toUpperCase()))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
private static List<ResourcePermissionType> doctorsPermissions() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
PATIENT_READ, PATIENT_WRITE
);
}
private static List<ResourcePermissionType> adminPermissions() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
BUILDING_UNIT_READ, BUILDING_UNIT_WRITE,
ORG_UNIT_READ, ORG_UNIT_WRITE
);
}
private static List<ResourcePermissionType> technicianPermission() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
BUILDING_UNIT_READ, BUILDING_UNIT_WRITE
);
}
}
ResourcePermissoinType.java
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ResourcePermissionType implements Serializable {
PATIENT_READ("PATIENT:READ"), PATIENT_WRITE("PATIENT:WRITE"),
ENCOUNTER_READ("ENCOUNTER:READ"), ENCOUNTER_WRITE("ENCOUNTER:WRITE"),
BUILDING_UNIT_READ("BUILDING_UNIT:READ"), BUILDING_UNIT_WRITE("BUILDING_UNIT:WRITE"),
ORG_UNIT_READ("ORG_UNIT:READ"), ORG_UNIT_WRITE("ORG_UNIT:WRITE");
#Getter
private String permissionName;
public static ResourcePermissionType of(final String permissionName) {
return Arrays.stream(values())
.filter(v -> v.getPermissionName().equals((permissionName.toUpperCase())))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
Unfortunately, the javax persistence API does not accept enums as entities. I tried using #Embeddable and #IdClass too, but that didn't work out for me either. I was not able to generate the schema that I had in mind. On the other hand, the schema was successfully generated using this model.
At the moment, both the Role repository as well as the Resource Permission repository are not exported (#RepositoryRestResource(..., exported = false)), so in order for you to persist those two entities, you'd have to provide that data in User. Keep that in mind, because that's also a part of the discussion that I want to talk about.
Now let's examine this integration test for the UserCrudRepository that will attempt to add a new user after a successful authentication.
#TestMethodOrder(OrderAnnotation.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
class UserCrudRepositoryApiITest {
private final List<User> testUsers = Arrays.asList(
new User().withUsername("dummy_username_01").withPassword("dummy_password_01").withAge(35)
.withRoles(new Role("ADMIN")),
new User().withUsername("dummy_username_02").withPassword("dummy_password_02").withAge(40)
.withRoles(new Role("DOCTOR")),
new User().withUsername("dummy_username_03").withPassword("dummy_password_03").withAge(45)
);
.
.
#Order(1)
#Test
public void afterAuthenticationAddNewUser() throws Exception {
final String generatedToken = login();
// serialize the user
final String requestJson = objectMapper.writeValueAsString(testUsers.get(0));
final RequestBuilder request = MockMvcRequestBuilders.post(USER_CRUD_BASE_URL)
.header(HttpHeaders.AUTHORIZATION, generatedToken)
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson);
final String serializedContent = mvc.perform(request)
.andExpect(status().isCreated())
.andReturn()
.getResponse()
.getContentAsString();
final User storedUser = objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(serializedContent, User.class);
assertThat(storedUser).isEqualTo(testUsers.get(0));
}
.
.
}
In here, I'm getting a status code conflict 409, and not able to persist all entities at once.
Unfortunately, SO allows only 30000 character, so please navigate to this repo if you would like to take a look at the log.
My Questions
I couldn't for the life of me understand where that referential integrity constraint violation
is occurring. Any idea?
Any suggestions on how to model these relations in a better way are welcome!
Another problem I'm having with JPA repos is that the only way to persist roles and resource permissions is by providing that data in the user's body. I would like those entities to be managed independently of the user (each with its own separate repository), so I tried exporting their repositories. However, the problem then is that you no longer can pass Role data in the body of a User, but rather A reference to that entity. Is there a way to get the best of both worlds.
I hope I made my problem clear, if not, I'd be happy to elaborate more.
I guess when a User is persisted, it also does the insert for the user_role table, but the role wasn't persisted yet. You could try to persist the Role first or use PERSIST cascading at the User#roles association.
I am using spring boot to model my backend and I have several foreign keys in my model structure.
I am unable to run delete operations in general and update operations when foreign keys are affected. I have tried several methods found on the internet but none seem to work.
Via SQL (SQL commands) can delete entities from book_queue_entry, copy and author_write but not author, book and book_store user due to foreign key constraints. Therefore I'd like to know what I am doing wrong in each of my models/relationships that don't work on SQL level.
Since it does not work on SQL level something with my modelling, at least as far as configuring on delete operations is fundamentally wrong. I can't point my finger at what.
Problem 1: Cannot delete a book: When a book is deleted, it should be deleted from the list of works of an author - in other words, it should be deleted from authors_write:
Author.java:
#Entity
#Setter
#Getter
#NoArgsConstructor
public class Author extends BaseEntity implements Serializable {
//stuff
#ManyToMany(cascade = CascadeType.ALL)
#JsonIgnore
#JoinTable(
name = "authors_write",
joinColumns = #JoinColumn(name = "author_id"),
inverseJoinColumns = #JoinColumn(name = "book_id"))
Set<Book> works;
}
Book.java
#Entity
#Setter
#Getter
#NoArgsConstructor
public class Book extends BaseEntity implements Serializable {
//stuff
#ManyToMany(mappedBy = "works", cascade = CascadeType.ALL)
#EqualsAndHashCode.Exclude
Set<Author> authors;
#JsonIgnore
#OneToMany(mappedBy = "book")
private List<BookQueueEntry> bookQueue;
}
Problem 2: Cannot delete Authors. When an author is deleted then all entries in authors_write with the corresponding author_id should be deleted. The classes are the same as above. Cascading does not work in this case either.
Problem 3: Cannot delete Users. When users are deleted borrower_id in copy should be nulled (I've read this does not work in JPA at all) and book_queue_entries with corresponding user_id should be deleted
Copy.java
#Entity
#Setter
#Getter
#NoArgsConstructor
public class Copy extends BaseEntity implements Serializable {
//stuff
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn
#EqualsAndHashCode.Exclude
private Book reference;
#ManyToOne
#JoinColumn
#EqualsAndHashCode.Exclude
private BookStoreUser borrower;
}
User.java:
public class BookStoreUser extends BaseEntity implements Serializable {
//more stuff here
#JsonIgnore
#OneToMany(mappedBy = "borrower")
private Set<Copy> booksBorrowed;
}
BookQueueEntry.java
#Entity
#Getter
#Setter
#NoArgsConstructor
#EqualsAndHashCode
public class BookQueueEntry extends BaseEntity implements Serializable {
//more stuff here
#ManyToOne(cascade = CascadeType.ALL)
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "user_id", nullable = false)
#EqualsAndHashCode.Exclude
private BookStoreUser user;
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "book_id", nullable = false)
#EqualsAndHashCode.Exclude
private Book book;
}
Once again, I believe something is wrong with how I've mapped the cascading operations. Something is wrong in a more general sense and I cant figure out what.
EDIT: I should note that #OnDelete(action = OnDeleteAction.CASCADE) did not work for me either.
EDIT 2: With a clear head, I've been able to fix Problems 1 and 2 using this: https://www.youtube.com/watch?v=vYNdjtf7iAQ
As noted in the comments, follow this video: https://www.youtube.com/watch?v=vYNdjtf7iAQ
My problems are solved but when deleting Users it throws a ConcurrentModificationException which is not really related to this problem.
My classes now look like this:
Book.java
public class Book extends BaseEntity implements Serializable {
#Column(unique = true)
private String isbn;
#NotBlank
private String title;
private Integer year;
private String imageUrl;
#ManyToMany(mappedBy = "works")
#EqualsAndHashCode.Exclude
Set<Author> authors;
private Boolean isAvailable;
private Integer version;
#Enumerated(EnumType.STRING)
private Language language;
#JsonIgnore
#OneToMany(mappedBy = "reference", cascade = CascadeType.REMOVE, orphanRemoval = true)
private Set<Copy> copies;
#JsonIgnore
#OneToMany(mappedBy = "book", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<BookQueueEntry> bookQueue;
// see here why: https://www.youtube.com/watch?v=vYNdjtf7iAQ
public void addAuthor(Author author) {
this.authors.add(author);
author.getWorks().add(this);
}
public void removeAuthor(Author author) {
this.authors.remove(author);
author.getWorks().remove(this);
}
}
Author.java:
public class Author extends BaseEntity implements Serializable {
private String name;
#ManyToMany
#JsonIgnore
#JoinTable(
name = "authors_write",
joinColumns = #JoinColumn(name = "author_id"),
inverseJoinColumns = #JoinColumn(name = "book_id"))
Set<Book> works;
public void addBook(Book book) {
this.works.add(book);
book.addAuthor(this);
}
public void removeBook(Book book) {
this.works.remove(book);
book.removeAuthor(this);
}
}
Copy.java:
public class Copy extends BaseEntity implements Serializable {
#ManyToOne
#JoinColumn
#EqualsAndHashCode.Exclude
private Book reference;
private LocalDate borrowedAt;
private LocalDate dueDate;
#ManyToOne
#JoinColumn
#EqualsAndHashCode.Exclude
private BookStoreUser borrower;
#NotNull
#Enumerated(EnumType.STRING)
private Location location;
public void addBorrower(BookStoreUser user) {
user.addBorrowedCopy(this);
}
public void removeBorrower(BookStoreUser user) {
user.removeBorrowedCopy(this);
}
}
BookStoreUser.java
public class BookStoreUser extends BaseEntity implements Serializable {
#NotNull
#Column(unique = true)
private String email;
private String firstName;
#NotNull
private String lastName;
private boolean isAdmin;
#JsonIgnore
#OneToMany(mappedBy = "borrower", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Copy> booksBorrowed;
#NotNull
private String password;
public void addBorrowedCopy(Copy copy) {
this.booksBorrowed.add(copy);
copy.setBorrower(this);
copy.setBorrowedAt(LocalDate.now());
copy.setDueDate(LocalDate.now().plusMonths(1));
}
public void removeBorrowedCopy(Copy copy) {
this.booksBorrowed.remove(copy);
copy.setBorrower(null);
copy.setBorrowedAt(null);
copy.setDueDate(null);
}
}
And also make sure to make use of the utility methods in the service layer like this:
public Long deleteById(Long id) {
this.validator.checkIDNotNull(id);
Book book = em.find(Book.class, id);
for (Author author : book.getAuthors()) {
book.removeAuthor(author);
}
bookRepository.deleteById(id);
validator.checkEntityNotExists(id);
return id;
}
public Book create(Book book) {
this.checkISBNIsValid(book.getIsbn());
this.checkISBNExists(book.getIsbn());
this.checkEntityHasValues(book);
for (Author author : book.getAuthors()) {
author.addBook(book);
}
log.info("Book with ISBN {} created successfully", book.getId());
return bookRepository.save(book);
}
EDIT: ConcurrentModificationException: Deleting from a list/Set will throw this problem. This can be found on SO - to fix ConcurrentModificationExceptions you have to create a new HashSet with whatever is throwing that problem and iterate through that:
new HashSet<Author>(book.getAuthors())
.forEach(author -> book.removeAuthor(author));
I have two classes, Role and Permission with a ManyToMany relationship between them. My problem is that each relationship has some extra data that comes with it therefore I believe I need to create an intermediary class to store these extra data, so that is the RolePermission class.
This is basically what I have, the parameter and domain are the extra data that are required for each relationship.
Here is the code I have right now for my classes.
Role.java
#Entity
#Table(name = "Sec_Role")
#DynamicUpdate
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String description;
private String name;
// This is another relationship which is working just fine (because there are no intermediary data needed.
#ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER)
private Set<Group> groups;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "role")
private List<RolePermission> permissions = new ArrayList<RolePermission>(0);
...Standard getters and setters and constructor
Permission.java
#Entity
#Table(name = "Sec_Permission")
public class Permission {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "permission")
private List<RolePermission> roles = new ArrayList<RolePermission>(0);
...Standard getters and setters and constructor
RolePermission.java
#Entity
#Table(name = "Sec_Role_Permission")
#DynamicUpdate
public class RolePermission {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int domain;
private String parameter;
#Column(updatable = false, insertable = false)
private int role_id;
#Column(updatable = false, insertable = false)
private int permission_id;
#ManyToOne(fetch = FetchType.LAZY )
#JoinColumn(name = "role_id", nullable = false)
private Role role;
#ManyToOne(fetch = FetchType.LAZY )
#JoinColumn(name = "permission_id", nullable = false)
private Permission permission;
...Standard getters and setters and constructor
Every class has a standard Repository like so
#RepositoryRestResource(path = "roles")
public interface RoleRepository extends CrudRepository<Role, Integer> {
}
Now this code works fine for reading data and relationships, my problem is that I cannot figure out what I am suppose to do or what end point to call to add/modify/delete a relationship between Role and Permission.
Currently if I call /roles/11/permissions I will get this back:
{
"_embedded": {
"rolePermissions": []
},
"_links": {
"self": {
"href": "http://localhost:8887/api/v1/roles/11/permissions"
}
}
}
How can I add a permission to this role?
I tried executing a POST request to /roles/11/permissions with the following JSON body and I got a 204 No Content response. This basically means success but then when I do a GET request to /roles/11/permissions I do not see permission with ID 1 there so it did not work.
{
"domain": 0,
"parameter": "Some param",
"role_id": 11,
"permission_id": 1
}
Since your mapping is itself an Entity you can model your API based on the Resource ( in this case RolePermission). Basically when you would want to provide an API to add rolepermission
Some thing like
http://localhost:8887/api/v1/rolepermission
POST
{
"roleid":"xxxxx"
"permissionid":"xxxxxx"
"parameterid":"xxxxxx"
"domain":"xxxxx"
}