Many to one relation with inherited objects - Postgres - spring

I'm building an application in which I need a single login page. However, there are two 'sorts' of users: Clients and Trainers. My domain looks like this:
User
|
+-----+-----+
| |
Person Trainer
My user will contain email, password etc. so I can use this table to verify my login requests.
A Trainer should store a list of persons, and a person should have 1 trainer.
The one to many side works, the trainer can store a list of Person class objects, but I can't link back to a trainer via a client.
This is my User class:
package com.example.demo.model;
import javax.persistence.*;
#Entity
#Inheritance
#Table(name="users")
public abstract class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
public User(){}
public User(String email) {
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
The Person class:
package com.example.demo.model;
import javax.persistence.*;
#Entity
public class Person extends User {
#Column
private int age;
#ManyToOne
#JoinColumn
private Trainer trainer;
public Person(){}
public Person(String email, int age) {
super(email);
this.age = age;
}
}
The Trainer class:
package com.example.demo.model;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
#Entity
public class Trainer extends User {
#Column
private String description;
#OneToMany(mappedBy = "trainer", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Person> clients;
public Trainer(){}
public Trainer(String email, String description) {
super(email);
this.description = description;
}
public void addClient(Person person){
this.clients = new ArrayList<>();
this.clients.add(person);
}
}
How should I access a client's trainer via the ManyToOne annotation? Or are there other options?
Thanks in advance!

Dont initialize list in addClient(). You are overwriting/deleting existing clients. Do it in field declaration.
#OneToMany(...)
private List<Person> clients = new ArrayList<>();
Set both sides of relation:
public void addClient(Person person){
person.setTrainer(this);
this.clients.add(person);
}

Related

SpringBoot StackOverflow error when getting entity

I have following problem. I'm new to Spring. I have created 2 entities and now using postman I want to get all books but I keep getting StackOverflowError.
Here is book model
package com.example.demo;
import jakarta.persistence.*;
import java.util.List;
#Entity
public class BookEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String title;
#ManyToMany
private List<Author> author;
public BookEntity() {
}
public BookEntity(String title) {
this.title = title;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<Author> getAuthor() {
return author;
}
public void setAuthor(List<Author> author) {
this.author = author;
}
}
Author class model
package com.example.demo;
import jakarta.persistence.*;
import java.util.List;
#Entity
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#ManyToMany
private List<BookEntity> book;
public Author() {
}
public Author(String name) {
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<BookEntity> getBook() {
return book;
}
public void setBook(List<BookEntity> book) {
this.book = book;
}
}
repository for books
package com.example.demo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface BookRepository extends JpaRepository<BookEntity, Long> {
}
repository for author
package com.example.demo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {
}
controller for books
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
#RequestMapping("/books")
public class BookController {
private final AuthorRepository authorRepository;
private final BookRepository bookRepository;
public BookController(AuthorRepository authorRepository, BookRepository bookRepository) {
this.authorRepository = authorRepository;
this.bookRepository = bookRepository;
}
#GetMapping
List<BookEntity> getAllBooks() {
return bookRepository.findAll();
}
}
Can you please explain what is happening? I can't get any further. I'm stuck
Well this is a common issue. The problem is that you have Book and Author related as ManyToMany. So now whenever you reach for Books, they have an Author field, and when Jackson is trying to add Author it turns out that Author has Books which again have an Author.
Im am aware of 2 ways out of here. First one is DTO you should create a class to be displayed by you controller looking somewhat like this:
public class BookDTO {
private long bookId;
private String bookTitle;
private List<AuthorDTO> authors;
// constructors getters setters
}
situation is a bit complicated because of Many to many so yo need another DTO for authors
public class AuthorDTO {
private long authorId;
private String authorName;
//constructors getters setters
}
you could use a service layer to do all of the mapping. Then you should return BookDTO in your controller.
Another way out are annotations:
#ManyToMany
#JsonManagedReference
private List<Author> author;
and
#ManyToMany
#JsonBackReference
private List<BookEntity> book;
#JsoonManaged and back References will stop Jackson from digging into another entity.
Another thing is you should consider mappedBy in one of your Entities to prevent creating 2 tables.

Passing JSON in body spring and constructing object with foreign key

I am trying to create a basic spring API.
I have a Post class that have an attribute User user as foreign key.
package com.example.demo.model;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import jakarta.persistence.*;
import java.util.Objects;
#Entity
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, updatable = false)
private Long id;
private String title;
private String body;
#ManyToOne
private User user;
public Post() {
}
public Post(String title, String body) {
this.title = title;
this.body = body;
}
// Getters and Settes ...
}
Here is the User class
package com.example.demo.model;
import jakarta.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
#Entity
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, updatable = false)
private Long id;
private String name;
private Integer age;
private String email;
#OneToMany(mappedBy = "user")
private List<Post> posts = new ArrayList<Post>();
#ManyToMany
#JoinTable(name = "user_task",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "task_id"))
private List<Task> tasks = new ArrayList<Task>();
public User() {}
public User(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// Getters and Settes ...
}
and here is my Post Controller
package com.example.demo.controller;
import com.example.demo.model.Post;
import com.example.demo.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
#RequestMapping("/post")
public class PostController {
private final PostService postService;
#Autowired
public PostController(PostService postService) {
this.postService = postService;
}
#GetMapping("/all")
public List<Post> getAllPosts (){
System.out.println("3");
return postService.getAllPosts();
}
#GetMapping("/{id}")
public Post getPost(#PathVariable Long id){
System.out.println("2");
return postService.getPost(id);
}
#PostMapping("/create")
public Post createPost(#RequestBody Post post){
return postService.createPost(post);
}
}
So in the /create endpoint i am passing a json object in the body. Here is an exemple:
{
"title": "Post1",
"body": "Post1 Body",
"user": "1"
}
the user: 1 is the foreign key to user who owns the post.
Here is the full error:
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.example.demo.model.User` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('1')]
I need to insert the json object into the Post table with the foreign key

Spring Boot Rest API Issues

I'm trying to implement a Spring Boot Rest API using Spring Data Jdbc with H2 Database.
This is a microservice, and I'm trying to send a POST request to the microservice from an angular app. I know my POST is working correctly from Angular. Inside of microservice, I am trying to save the POST request to a local H2 database.
This should be relatively straight forward based on documentation I've read online, but I am getting error messages. Any help would be greatly appreciated. Here are the files I have setup inside my spring boot microservice (titled 'order'):
OrderController.java:
package com.clothingfly.order;
import java.util.ListIterator;
import org.springframework.web.client.RestTemplate;
import com.clothingfly.order.Model.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.clothingfly.order.Model.Order;
#RestController
#CrossOrigin(origins = "http://localhost:4200")
public class OrderController {
#Autowired
TempOrderRepository orderRepository;
#PostMapping("/order")
public Order postOrder(#RequestBody Order order) {
Order _order = orderRepository.save(new Order(order.getId(), order.getAddress(), order.getPayment(), order.getItems()));
return _order;
}
}
TempOrderRepository.java:
package com.clothingfly.order;
import org.springframework.data.jpa.repository.JpaRepository;
import com.clothingfly.order.Model.Order;
public interface TempOrderRepository extends JpaRepository<Order, Long>{
}
OrderApplication.java:
package com.clothingfly.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
And I have a model named Order.java:
package com.clothingfly.order.Model;
import java.util.List;
import javax.persistence.*;
import org.springframework.data.annotation.Id;
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "Address")
private Address address;
#Column(name = "Payment")
private PaymentInfo payment;
#Column(name = "Items")
private List<Item> items;
#Column(name = "Error")
private String error;
public Order() {
}
public Order(long id, Address address, PaymentInfo payment, List<Item> items){
this.id = id;
this.address = address;
this.payment = payment;
this.items = items;
this.error = "";
}
public long getId() {
return id;
}
public Address getAddress() {
return address;
}
public PaymentInfo getPayment() {
return payment;
}
public List<Item> getItems() {
return items;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
The Order model takes in three other models:
Item.java:
package com.clothingfly.order.Model;
import javax.persistence.*;
#Entity
#Table(name = "items")
public class Item {
#Id
#GeneratedValue
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#Column(name = "price")
private float price;
#Column(name = "imageUrl")
private String imageUrl;
#Column(name = "quantity")
private long quantity;
#Column(name = "inventory")
private long inventory;
public long getId() {
return id;
}
public String getName() {
return name;
}
public float getPrice() {
return price;
}
public long getQuantity() {
return quantity;
}
public long getInventory() {
return inventory;
}
public String getImageUrl(){
return imageUrl;
}
public void setInventory(long inventory) {
this.inventory = inventory;
}
public void setName(String name) {
this.name = name;
}
public void setPrice(float price) {
this.price = price;
}
public void setQuantity(long quantity) {
this.quantity = quantity;
}
public Item(long id, String name, float price, long quantity, long inventory, String imageUrl) {
this.id = id;
this.name = name;
this.price = price;
this.quantity = quantity;
this.inventory = inventory;
this.imageUrl = imageUrl;
}
public Item() {
}
}
Address.java:
package com.clothingfly.order.Model;
import javax.persistence.*;
#Entity
#Table(name = "addresses")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#Column(name = "address")
private String address;
#Column(name = "country")
private String country;
#Column(name = "apartmentNo")
private String apartmentNo;
#Column(name = "state")
private String state;
#Column(name = "city")
private String city;
#Column(name = "zipcode")
private String zipcode;
public Address() {
}
public Address(String firstName, String lastName, String address, String country, String apartmentNo, String state,
String city, String zipcode) {
this.firstName = firstName;
this.lastName = lastName;
this.address = address;
this.country = country;
this.apartmentNo = apartmentNo;
this.state = state;
this.city = city;
this.zipcode = zipcode;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getApartmentNo() {
return apartmentNo;
}
public void setApartmentNo(String apartmentNo) {
this.apartmentNo = apartmentNo;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
}
PaymentInfo.java:
package com.clothingfly.order.Model;
import javax.persistence.*;
#Entity
#Table(name = "payments")
public class PaymentInfo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "cardHolder")
private String cardHolder;
#Column(name = "cardNumber")
private String cardNumber;
#Column(name = "expirationDate")
private String expirationDate;
#Column(name = "cvv")
private String cvv;
public PaymentInfo(String cardHolder, String cardNumber, String expirationDate, String cvv) {
this.cardHolder = cardHolder;
this.cardNumber = cardNumber;
this.expirationDate = expirationDate;
this.cvv = cvv;
}
public String getCardHolder() {
return cardHolder;
}
public void setCardHolder(String cardHolder) {
this.cardHolder = cardHolder;
}
public String getCardNumber() {
return cardNumber;
}
public void setCardNumber(String cardNumber) {
this.cardNumber = cardNumber;
}
public String getExpirationDate() {
return expirationDate;
}
public void setExpirationDate(String expirationDate) {
this.expirationDate = expirationDate;
}
public String getCvv() {
return cvv;
}
public void setCvv(String cvv) {
this.cvv = cvv;
}
}
I'm getting the following error when trying to run microservice:
Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: com.clothingfly.order.Model.Address, at table: orders, for columns: [org.hibernate.mapping.Column(address)]
How would I go about fixing this?
I want to be able to display all of my models inside a table.
I tried changing Address model so that it only returns a string of the city, but that seemed to cause more issues than anything.
Note, one to one will always cause you an issue, always better to implement many to one and one to many. and add it to both entities you are mapping. It will do the same job with no errors.
First, create packages, and don't place everything in one package.
create
package com.clothingfly.repo or com.clothingfly.order.repo
package com.clothingfly.controller or com.clothingfly.order.controller
Secondly, add the annotation #Repository to your repository interface
package com.clothingfly.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import com.clothingfly.order.Model.Order;
#Repository
public interface TempOrderRepository extends JpaRepository<Order, Long>{
}
Thirdly, add the annotation #EnableJpaRepositories(basePackages = "com.clothingfly.repo") to your main application class.
package com.clothingfly.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
#EnableJpaRepositories(basePackages = "com.clothingfly.repo")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
Lastly, this will not work correctly. Not sure what you are doing here.
#Column(name = "Address")
private Address address;
Try this:
add this in your address entity
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name="order_id", nullable = false)// add this column
// order_id in your Address database not the entity
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Order order;
Then, add this to your Order class.
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy="order")
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
List<Address> address = new ArrayList<Address>(); // I use list cause sometimes it throws an error
// try to simply use this, if it throws expection then use the list
//private Address address;
Your Order constructor should be like:
public Order(long id, List<Address> address ...etc
Or simply
public Order(long id, Address address ...etc
Do this for all your mapped entities and don't forget to add setters and getters for all fields.
you have to tell hibernate that the Address object is coming from another table and how to join those tables, since your orders table most likely does not have a column which contains the hole address but the the address id/ primary key of the address as foreign key.
this is possible, depending if you have 1:1, 1:n, n:1 or n:m relations with the corresponding #OneToOne, #OneToMany, #ManyToOne and #ManyToMany annotations.
for your example it could be something like
#OneToOne
#JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;

Spring (Hibernate) - incomplete serialization result / many-to-many

Rest Application / Spring MVC - 3 entities: User, AccessRole, AccessPermision.
Each user has only one role, each role has one or more privileges.
The problem occurs during serialization of users with the same role.
In such case, the JSON serialization result, contains permissions only for the first user.
User Entity
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import socialcreek.access.model.AccessRole;
import socialcreek.user.views.UserViews;
import javax.persistence.*;
import java.util.Set;
#Entity
#Table(name = "users")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class User {
/**-----------------------------------------------------
* Constructor
-------------------------------------------------------*/
public User(){ }
public User(String username, String password, AccessRole accessRole) {
this.username = username;
this.password = password;
this.userAccessRole = accessRole;
}
/**-----------------------------------------------------
* Entity Properties
-------------------------------------------------------*/
#Column()
#Id
#JsonView(UserViews.BasicView.class)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JsonView(UserViews.BasicView.class)
private String username;
private String password;
#JsonView(UserViews.BasicView.class)
#ManyToMany(mappedBy = "users",fetch=FetchType.EAGER)
private Set<UsersGroup> usersGroups;
#ManyToOne(targetEntity = AccessRole.class, optional = false,fetch = FetchType.EAGER, cascade=CascadeType.MERGE)
#JoinColumn(name = "user_role")
#JsonView(UserViews.BasicView.class)
private AccessRole userAccessRole;
/**-----------------------------------------------------
* Setters & Getters
-------------------------------------------------------*/
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<UsersGroup> getUsersGroups() {
return usersGroups;
}
public void setUsersGroups(Set<UsersGroup> usersGroups) {
this.usersGroups = usersGroups;
}
public AccessRole getUserAccessRole() {
return userAccessRole;
}
public void setUserAccessRole(AccessRole userAccessRole) {
this.userAccessRole = userAccessRole;
}
}
AccessRole Entity
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import socialcreek.user.views.UserViews;
import javax.persistence.*;
import java.util.Set;
#Entity
#Table(name = "access_role")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class AccessRole {
/**-----------------------------------------------------
* Entity Properties
-------------------------------------------------------*/
#Id
#JsonView(UserViews.BasicView.class)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JsonView(UserViews.BasicView.class)
private String roleName;
#JsonView(UserViews.BasicView.class)
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinTable(name = "access_role_permissions")
private Set<AccessPermission> accessPermissions;
/**-----------------------------------------------------
* Setters & Getters
-------------------------------------------------------*/
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Set<AccessPermission> getAccessPermissions() {
return accessPermissions;
}
public void setAccessPermissions(Set<AccessPermission> accessPermissions) {
this.accessPermissions = accessPermissions;
}
}
AccessPermission Entity
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import socialcreek.user.views.UserViews;
import javax.persistence.*;
#Entity
#Table(name = "access_permission")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class AccessPermission {
/**-----------------------------------------------------
* Entity Properties
-------------------------------------------------------*/
#Id
#JsonView(UserViews.BasicView.class)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JsonView(UserViews.BasicView.class)
private String permissionName;
/**-----------------------------------------------------
* Setters & Getters
-------------------------------------------------------*/
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPermissionName() {
return permissionName;
}
public void setPermissionName(String permissionName) {
this.permissionName = permissionName;
}
}
Serialization Result:
[ { "id":70, "username":"admin", "usersGroups":[], "userAccessRole":{
"id":68, "roleName":"ROLE_ADMIN", "accessPermissions":[
{
"id":69,
"permissionName":"FULL_ACCESS"
}]} },
{ "id":71, "username":"admin2", "usersGroups":[], "userAccessRole":68}
]
Please, have a look at accessRole and accessPermision information - it's complete only for the user:admin. In case of user:admin2 there is only information about accessRoleId ( no information about roleName, accessPermision)
It happens only when both users have the same accessRole. If I change accessRole of user:admin2 to another role - everythnink will be ok.
I found the similar issue with the correct answer (https://stackoverflow.com/a/27117097/4694022).
The problem is caused by #JsonIdentityInfo. After I removed it - it works ...now I need to find the solution to handle serialization for recursive structure but it's another story ...

spring JPA users, roles authentication - how do I avoid duplicate role entries?

Hi I'm new to spring JPA and spring security, I've spent a while trying to get authentication working based on users and roles.
A user should be able to have many roles, and a role can belong to many users.
There should only be about 4 roles.
I've tried to achieve this with a #Manytomany mapping and a join table.
It more or less works, but when I try to persist a new user with a set of Roles it will add duplicate entries to the roles table (or if I put a unique key on the roles table hits a UK violation).
here's the code:
user
import java.util.Set;
import javax.persistence.*;
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String email;
private String password;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinTable(joinColumns = #JoinColumn(name = "id"),inverseJoinColumns = #JoinColumn(name = "roleId"))
private Set<Role> roles;
public User(String email, String password, Set<Role> roles) {
super();
this.email = email;
this.password = password;
this.roles = roles;
}
}
role
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String role;
#ManyToMany(mappedBy = "roles",fetch = FetchType.LAZY)
private Set<User> users;
public Role(String role) {
super();
this.role = role;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Set<User> getUsers() {
return users;
}
public void setUser(Set<User> users) {
this.users = users;
}
}
ManyToMany is the correct logical relationship type. The reason you are getting too many roles is probably that you create new instances of Role, but if the role already exist in the database, you must load it into the persistence context, and only create a new role if it does not exist.
It would look something like this if you managed the transactions in code, if you're using container managed persistence with #Transactional you should remove the transaction code, and the try-finally block.
try {
List<String> roleNames = Lists.newArrayList("user, admin, superuser");
User user = new User("Peter", "Pan");
List<Role> roles = new ArrayList<>();
entityManager.getTransaction().begin();
for (String roleName : roleNames) {
List<Role> found = entityManager.createQuery("select r from Roles r where r.name = :roleName", Role.class)
.setParameter("roleName", roleName).getResultList();
if (found.isEmpty()) {
Role role = new Role(roleName);
entityManager.persist(role);
roles.add(role);
} else {
roles.addAll(found);
}
}
user.setRoles(roles);
entityManager.persist(user);
entityManager.getTransaction().commit();
} finally {
entityManager.close();
}
Personally I almost never use JPA's ManyToMany, I typically break it down to 2 OneToManyrelations, that way I have an Entity for the mapping table which is otherwise magically created by JPA. This also gives you additional control, for instance if you need to modify or delete a role from a user, you can delete the UserRoleMapping entity directly, instead of having to manipulate list on Role and User. It also giver you more control over cascading.

Resources