I'm setting up spring security, yet when I define a #OneToMany relationship I get the error:
Use of #OneToMany or #ManyToMany targeting an unmapped class: loginApi.users.Users.roles[Security.Roleinfo]
I've already tried most of the answers on here,
yes, I am importing from javax.persistance
yes, #Entity is on top of each class
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users")
public class Users {
#Id
#Column(name="username")
private String username;
private String firstname;
private String lastname;
private int curreventid;
private String email;
private String password;
private int points;
private int phone;
// problem part
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "usersrole", joinColumns = {#JoinColumn(name = "username")}, inverseJoinColumns = {#JoinColumn(name = "roleid")})
private Set<Roleinfo> roles;
}
Here is the roleinfo class:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "roleinfo")
public class Roleinfo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int roleid;
private String roletitle;
}
right now I'm trying to join my usersrole table which has two columns containing the username and that user's role (roleid) with the user table.
any help would be appreciated!
Thanks #JB Nizet, after reading the docs I realized my security package wasn't under my springbootapp package, meaning the user class was never scanned for the #Entity flag.
Related
I am working on a springboot application. I have 2 entity classes, Group and User. I also have #ManyToMany relationship defined in the Group class (Owning entity), and also in the User class, so that I can fetch all the groups a user belongs to. Unfortunately, I can't create a new group or a new user due to the following error;
{
"timestamp": "2022-09-09T20:29:22.606+00:00",
"status": 415,
"error": "Unsupported Media Type",
"message": "Content type 'application/json;charset=UTF-8' not supported"
}
When I try to fetch all groups a user belongs to by calling user.get().getGroups(); I get a a stack overflow error
Note: Currently I have #JsonManagedReference and #JsonBackReference in Group and User classes respectively. I also tried adding #JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") on both classes, but this did not work either. Adding value parameter to #JsonManagedReference and #JsonBackReference as demonstrated below did not work either. What am I doing wrong? What am I missing?
This is my Group entity class
#Table(name = "`group`") // <- group is a reserved keyword in SQL
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JsonView(Views.Public.class)
private String name;
private Integer maximumMembers;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
#JoinTable(name = "group_user", joinColumns = #JoinColumn(name = "group_id"), inverseJoinColumns = #JoinColumn(name = "user_id"))
#JsonView(Views.Public.class)
#JsonManagedReference(value = "group-member")
private Set<User> groupMembers;
}
This is my User entity class
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(Views.Public.class)
private Long id;
#JsonView(Views.Public.class)
private String nickname;
#JsonView(Views.Public.class)
private String username; // <- Unique user's phone number
private String password;
#ElementCollection(targetClass = ApplicationUserRole.class)
#CollectionTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"))
#Enumerated(EnumType.STRING)
#Column(name = "role")
private Set<ApplicationUserRole> roles;
#ManyToMany(mappedBy = "groupMembers", fetch = FetchType.LAZY, targetEntity = Group.class)
#JsonBackReference(value = "user-group")
private Set<Group> groups;
}
Minimal, Reproducible Example https://github.com/Java-Techie-jt/JPA-ManyToMany
I found a permanent solution for this problem. For anyone else facing a similar problem, This is what I found. First, my entity classes had #Data Lombok annotation. I removed this because the #Data annotation has a tendency of almost always loading collections even if you have FetchType.LAZY.
You can read more about why you should't annotate your entity class with #Data here https://www.jpa-buddy.com/blog/lombok-and-jpa-what-may-go-wrong/
After removing this annotation, I removed #JsonManagedReference and #JsonBackReference from both sides of the relationship(both entities). I then added #Jsonignore to the referencing side only(User class). This solves 2 things
Creating a group with a list of users works fine
Adding a list of users to a group works fine.
After this, we are left with one last problem. When we try to read a user from the api, we get a user without the associated list of groups they belong to, because we have #JsonIgnore on the user list. To solve this, I made the controller return a new object. So after fetching the user from my service, I map it to a new data transfer object, the I return this object in the controller.
From here I used #JsonView to filter my responses.
This is how my classes look, notice there is no #Data in annotations.
Group
#Builder
#Entity
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Getter
#Setter
#Table(name = "`group`") // <- group is a reserved keyword in SQL
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private Integer maximumMembers;
#ManyToMany(fetch = FetchType.EAGER,
cascade = {CascadeType.MERGE, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "group_user",
joinColumns = #JoinColumn(name = "group_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
#JsonView(UserViews.PublicUserDetails.class)
private Set<User> groupMembers = new HashSet<>();
}
User
#Builder
#Entity
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Getter
#Setter
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(UserViews.PublicUserDetails.class)
private Long id;
#JsonView(UserViews.PublicUserDetails.class)
private String nickname;
#JsonView(UserViews.PublicUserDetails.class)
private String username; // <- Unique user's phone number
private String password;
#ElementCollection(targetClass = ApplicationUserRole.class)
#CollectionTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"))
#Enumerated(EnumType.STRING)
#Column(name = "role")
#JsonView(UserViews.PublicUserDetails.class)
private Set<ApplicationUserRole> roles;
#JsonIgnore
#ManyToMany(mappedBy = "groupMembers", fetch = FetchType.LAZY, targetEntity = Group.class)
private Set<Group> groups = new HashSet<>();
}
Method fetching user in user controller
#GetMapping("/get-groups")
public ResponseEntity<UserRequestResponseDTO> getWithGroups(#RequestParam(name = "userId") Long userId) {
User user = userService.getWithGroups(userId);
UserRequestResponseDTO response = UserRequestResponseDTO.builder()
.nickname(user.getNickname())
.username(user.getUsername())
.groups(user.getGroups())
.build();
return ResponseEntity.ok().body(response);
}
Hopefully this helps someone💁
I've just found an example of the #ManyToMany relationship of Hibernate on github which made me wonder what was the correct way to implement it. Let me explain better. The use case is very simple: I have a user which can have one or more roles. I initially thought that this relationship was a many to many relationship, but I found this example which confuses me a lot:
Users.java class
#Entity
#Table(name = "user")
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#Column(name = "email")
private String email;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
public Users() {
}
public Users(Users users) {
this.email = users.getEmail();
this.roles = users.getRoles();
this.id = users.getId();
}
`// getters and setters
Role.java class
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "role_id")
private int roleId;
#Column(name = "role")
private String role;
public Role() {
}
// getters and setters
First of all it uses a OneToMany relationship (and I can't explain why), and second it doesn't have a reference to Users inside the Role class. Does someone know why and can explain it to me? I would have gone with a ManyToMany relationship and I would have put a Set of users also inside Role with a #ManyToMany annotation. Here the link to the git repo.
I have the following two classes.
School has many TestTakers
#Entity
#Table(name = "school")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class School extends BaseModel {
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "school")
// #JsonManagedReference <<<<< If not commented out, then error
private Set<TestTaker> testTakers;
//getter setters
}
// TestTaker.java
#Entity
#Table(name = "test_taker")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class TestTaker extends BaseModel {
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#ManyToOne
#JoinColumn(name = "school_id")
#JsonBackReference("school_testTaker")
private School school;
//getters setters
}
Can anyone explain why #JsonManagedReference cannot be annotated on a collection? I would get an error saying Jackson cannot handle managed/back reference. How exactly does #JsonManagedReference work with #JsonBackReference in common DB relationships One-to-one, One-to-many, Many-to-one
I have read the documentation, still don't quite understand what Jackson is trying to achieve
I'm trying to figure out what I'm doing wrong with this, but I'm learning hibernate annotations and creating a simple library system. Basically, a book gets checked out by a person, and eventually checked in. Here's how I have it configured:
#Entity
#Table
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private long barcode;
#Column(nullable = false)
private String name;
#OneToMany
#JoinTable(name = "checkoutsession", joinColumns = { #JoinColumn(name = "book") }, inverseJoinColumns = { #JoinColumn(name = "id")})
private List<CheckOutSession> checkOutSessions;
}
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false, unique = true)
private long barcode;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
#OneToMany
#JoinTable(name = "checkoutsession", joinColumns = { #JoinColumn(name = "user") }, inverseJoinColumns = { #JoinColumn(name = "id")})
private List<CheckOutSession> checkOutSessions;
}
#Entity
#Table(name = "checkoutsession", uniqueConstraints = {#UniqueConstraint(columnNames={"book", "checkIn"})})
public class CheckOutSession {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name="book", nullable=false)
private Book book;
#ManyToOne
#JoinColumn(name="user", nullable=false)
private User user;
#Column(nullable = false)
private java.sql.Timestamp checkOut;
#Column
private java.sql.Timestamp checkIn;
}
I can't figure out for the life of me what I've got configured incorrectly.
[EDIT]
when I try to pull a book it is selecting everything from checkoutsession join checkoutsession join user and dies saying "Unknown column checkoutsess1_.check_in in 'field list';
[EDIT2]
A little more context, I have a BookDAO that extends JpaRepository and when I call findAll() is what's creating that query.
[EDIT3]
Rest Class:
#RestController
#RequestMapping("rest/books")
public class BookController {
#RequestMapping(method = RequestMethod.GET)
public List findBooks() {
return bookService.getAllBooks();
}
}
Service:
#Component
public class BookService {
private BookDao bookDao;
public List getAllBooks() {
return bookDao.findAll();
}
#Autowired
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
DAO:
public interface BookDao extends JpaRepository<Book, Long> {
}
Thanks for any help!
If I run your code and make JPA generate tables based on the entities it seems to work (at least, it does run).
However, your mappings appear to be odd to me, more specifically the #JoinTable annotation. The #JoinTable annotation is commonly used when you have a join table (eg. checkoutSession in your case), but you don't want to map it because it contains no useful information except the links between those two tables.
In that case, you use the #JoinTable annotation as following:
#ManyToMany
#JoinTable(
name = "checkoutsession", // Name of the join table
joinColumns = #JoinColumn(name = "book"), // The column name in checkoutsession that links to book
inverseJoinColumns = #JoinColumn(name = "user") // The column name in checkoutsession that links to user
)
private List<User> users;
So in this case, you can directly link the Book and User entity without having to create the CheckoutSession entity.
However, in your case your join table also contains two timestamps, if you need those in your application, then you don't have to use #JoinTable but simply use #JoinColumn to link them, for example:
#OneToMany(mappedBy = "book") // The field name in CheckoutSession that links to book
private List<CheckoutSession> checkOutSessions;
This is what you should have in your Book entity. Be aware that in this case we're talking about field names not about column names. You have to enter the name of the field in CheckoutSession that maps back to the Book entity.
For more information about the #JoinTable annotation I recommend you to read this answer: JPA "#JoinTable" annotation or this article.
Is this possible? Haven't seen much discussion on it.
Sure! It works great from my experience. Here's an example entity:
#Entity
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class PingerEntity {
// ID
#Id
#Getter
#Setter
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// USER
#Getter
#Setter
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private UserEntity user;
// URL
#Getter
#Setter
#Basic(optional = false)
private String url;
/**
* The number of seconds between checks
*/
#Getter
#Setter
#Basic(optional = false)
private int frequency;
#Getter
#Setter
#Basic(optional = false)
#Enumerated(EnumType.STRING)
public MonitorType monitorType;
}
You can use it also with #Data (and it works !)
#Entity
#Data
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
}
I have never tried Lombok with Hibernate but I don't see why it shouldn't work.
Also, take a look here: http://groups.google.com/group/project-lombok/browse_thread/thread/294bd52d9d8695df/7bc6b0f343831af1?lnk=gst&q=hibernate#7bc6b0f343831af1
Also, Lombok project release notes mention Hibernate explicitely.
A simple example; Library.java:
#Data
#NoArgsConstructor // JPA
#Entity
#Table(name = "libraries")
public class Library {
#Id
#GeneratedValue
private Long id;
#OneToMany(cascade = CascadeType.ALL)
#EqualsAndHashCode.Exclude
// This will be included in the json
private List<Book> books = new ArrayList<>();
#JsonIgnore
public void addBook(Book book) {
books.add(book);
book.setLibrary(this);
}
}
And Book.java:
#Data
#NoArgsConstructor // JPA
#Entity
#Table(name = "books")
public class Book {
#Id
#GeneratedValue
private Long id;
#NotBlank
private String title;
#ManyToOne
#JoinColumn(name = "library_id") // Owning side of the relationship
#EqualsAndHashCode.Exclude
#JsonIgnore // Avoid infinite loops
private Library library;
}