Mapping objects with a mapping entity - java

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.

Related

I am unable to create entity or fetch list of entities due to stack overflow error on bi-directional #ManyToMany relationship SpringDataJPA

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💁

How to audit a #JoinTable with #ManyToMany

I'm working on a Spring-Boot project with a H2 database. I have two entities Portfolio and Report, and there is a many-to-many association between the two.
I want those entities to be audited, so I followed this tutorial to audit through an AuditorAware interface with custom fields.
The two entities are well audited, the columns are created in the database. However, the join table portfolio_reports is not audited. How can I audit the join table as well ?
Portfolio.java
#Entity
#Table(name = "portfolio")
public class Portfolio extends Auditable<String> implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
#Unique
private String name;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(name = "portfolio_report", joinColumns = #JoinColumn(name = "portfolio_id"), inverseJoinColumns = #JoinColumn(name = "report_id"))
private List<Report> reports;
// Getters and setters
}
Report.java
#Entity
#Table(name = "report")
public class Report extends Auditable<String> implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "axioma_id")
private Long axiomaId;
#Column(name = "name")
private String name;
#AuditJoinTable
#ManyToMany(mappedBy = "reports", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
private List<Portfolio> portfolios;
// Getters and setters
}
Auditable.java
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<U> {
#Version
#Column(name = "version_no")
protected Long versionNo;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date")
protected Date createdDate;
#LastModifiedDate
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "modified_date")
protected Date modifiedDate;
}
AuditorAwareImpl.java
public class AuditorAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
return Optional.of("Admin");
}
}
PersistenceConfiguration.java
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class PersistenceConfiguration {
#Bean
public AuditorAware<String> auditorAware() {
return new AuditorAwareImpl();
}
}
Problem:
Clearly here Auditable should add some column to your intermediate table that maintains relation between Portfolio and Report and that table is created behind the scene and you don't have access to that table in your program. Only hibernate can use that table to maintain relation between your entities and do join operation.
Solution:
Here you should make Join table that maintain Many to Many relation between Portfolio and Report explicit so that you can have entity like PortfolioReport in your program that can extends from Auditable. Please read the following post to see how to do that: The best way to map a many-to-many association with extra columns when using JPA and Hibernate

What is the most efficient way to map #ManyToMany relationship with JPA and Hibernate?

I know only basics of DB and JPA/Hibernate. I have to manage a User table, where a user can have many roles. The roles are contained in a catalog table which in my User formulary i do not pretend to manage/modify, i just need the catalog values as a reference to add or delete to my user.
I think the best approach would be to create a relationship table between User and Role to hold the users and their roles 'User_Roles' (unless there is a more efficient approach).
I am not allowed to modify the Role entity since it is used for different purposes in a lot of other areas of my app that are independent of the User table.
I've seen a lot of examples but I still do not know which one exactly aplies to my specific needs. How can I map my User and its roles in a sigle Entity with JPA and Hibernate?
Maybe the next image describes better what I want:
Thank you very much in advance for your answers.
In your case you have to use #ManyToMany to associate both tables.
That should look at this:
#Entity
#Table(name = "User")
public class User {
...
#ManyToMany
#JoinTable(name = "User_Roles", joinColumn = "id_person")
private Set<Role> roles = new HashSet<>;
}
#Entity
#Table(name = "Role")
public class Role {
...
#ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>;
}
What you're describing is a one-to-many relationship but it's between User and the joining table - User_Roles. Since there is not much you can do to avoid the joining table, the best thing would be to use #ManyToMany with #JoinTable annotations to map the relationship. Remember to use Set instead of List. You don't need an entity for the joinint table then.
You can find a discussion about this topic in this blog post.
As per your above screen, what I understood user can be assigned more than 1 role.
i.e. 1 user can be mapped to multiple role and 1 role can be mapped to multiple users.
Hence relationship between user and role is many to many.
many to many relationship can be achieved using third table which is called mapping table.
so , we have following tables in your example :-
user
user_roles
role
#Entity
#Table(name = "user")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
#Id
#SequenceGenerator(name = "USER_ID_GENERATOR", sequenceName = "USER_SEQ",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_ID_GENERATOR")
#Column(name = "user_id")
private Long userId;
#OneToOne
#JoinColumn(name = "persion_id")
private person person;`
enter code
here`
#Basic
#Column(name = "date")
private Date date;
#Basic
#Column(name = "observations")
private String observations;
#Basic
#Column(name = "text")
private String text;
#JsonIgnore
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<UserRoles> users = new ArrayList<>();
}
#Entity
#Table(name = "role")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Role {
#Id
#SequenceGenerator(name = "ROLE_ID_GENERATOR", sequenceName = "ROLE_SEQ",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ROLE_ID_GENERATOR")
#Column(name = "role_id")
private Long roleId;
#Basic
#Column(name = "id1")
private Long idOne;
#Basic
#Column(name = "id1")
private Long idTwo;
#Basic
#Column(name = "id1")
private Long idThree;
#Basic
#Column(name = "text")
private String text;
#JsonIgnore
#OneToMany(mappedBy = "role", cascade = CascadeType.ALL)
private List<UserRoles> users = new ArrayList<>();
}
#Entity
#Getter
#Setter
#Table(name = "user_roles")
#JsonInclude(JsonInclude.Include.NON_NULL)
#Audited
public class UserRoles {
private static final long serialVersionUID = 1L;
#EmbeddedId
UserRolesKey userRoleId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("role_id")
#JoinColumn(name = "role_id")
Roles role;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("user_id")
#JoinColumn(user_id)
User user;
#PrePersist
private void prePersist() {
super.onPrePersist();
if (this.getId() == null) {
UserRolesKey mapKey = new UserRolesKey();
mapKey.setRoleId(this.getRole().getRoleId());
mapKey.setUserRoleId(this.getUser().getUserId());
this.setId(mapKey);
}
}
}
While saving you just need to populate user entity with all the uaerRoles mapping entity and persist it. jpa will save all the details.
while updating role assign to user you need to fetch the user entity and update the mapping by adding new userRoles entity and nullifying the while is going to be removed.

Hibernate annotations: Many-to-many with shared composite key attribute

I'm trying to map up an existing database schema using Hibernate+JPA annotations.
One of my entities are mapped like this:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
private int department;
#Id
private int userId;
...
And another entity, Group:
#Entity
#Table(name = "groups")
public class Group implements Serializable {
#Id
private int department;
#Id
private int groupId;
...
Group and User should have a many-to-many relationship between them, but the issue is that the join table ("user_group") only has columns "DEPARTMENT, USERID, GROUPID" - i.e. the DEPARTMENT column needs to be used in both joinColumns and inverseJoinColumns:
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "user_groups",
joinColumns = { #JoinColumn(name = "department"), #JoinColumn(name = "groupid") },
inverseJoinColumns = {#JoinColumn(name = "department"), #JoinColumn(name = "userid") }
)
private List<User> groupUsers = new ArrayList<>();
which gives a mapping error - "Repeated column in mapping for entity".
However, it looks like this was/is possible using XML, because this exact example exists in the old Hibernate documentation. But I cannot find any evidence that this ever worked using annotations? I tried with #JoinFormula instead of #JoinColumn, but that does not compile. Is it possible?
Okay, I'm pretty sure it's not possible.
I found a promising workaround:
Create an #Embeddable for the "user_group" table:
#Embeddable
public class UserGroupMembership implements Serializable {
#ManyToOne
#JoinColumnsOrFormulas(
value = {
#JoinColumnOrFormula(column = #JoinColumn(referencedColumnName = "userid", name = "userid")),
#JoinColumnOrFormula(formula = #JoinFormula(referencedColumnName = "department", value = "department"))
})
private User user;
public UserGroupMembership(User user) {
this.user = user;
}
public UserGroupMembership() {
}
public User getUser() {
return user;
}
}
The trick is that #ManyToOne allows you to use #JoinColumnsOrFormulas, so one of the join conditions can be a formula, which I doesn't seem to work for #ManyToMany (the #JoinColumnsOrFormulas annotation is ignored as it expects the join columns to be part of the #JoinTable annotation).
The UserGroupMemberships are then mapped as a ElementCollection:
#ElementCollection
#CollectionTable(name = "user_group", joinColumns = {
#JoinColumn(name = "department", referencedColumnName = "department"),
#JoinColumn(name = "groupid", referencedColumnName = "groupid")
})
#OrderColumn(name = "seq", nullable = false)
private List<UserGroupMemberships> groupUsers = new ArrayList<>();
This only works right now for a unidirectional many-to-many relationship.

Hibernate creating relation on #JoinTable

I have two tables which have Many-to-Many relations which have a JoinTable USER_SERVICES as below.
#Entity
public class User implements Serializable {
#NotNull
#Column(unique=true)
private String username;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "USER_SERVICES",
joinColumns = {#JoinColumn(name = "id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "", referencedColumnName = "name")})
private Set<Services> services;
// Getters and Setters
}
#Entity
public class Services implements Serializable {
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long serviceId;
#NotNull
#Column(unique=true)
private String name;
//Getters and Setters
}
The above code creates a table USER_SERVICES, but I also want to have a Many-to-Many relation on the table USER_SERVICES with another table RATINGS which would result in another table USER_SERVICES_RATINGS. how can I define this relation with Hibernate/JPA annotations?
Bi-Directional Many to Many using user managed join table object (Relatively common)
Commonly used when you want to store extra information on the join object such as the date the relationship was created.
public class Foo{
private UUID fooId;
#OneToMany(mappedBy = "bar", cascade=CascadeType.ALL)
private List<FooBar> bars;
}
public class Bar{
private UUID barId;
#OneToMany(mappedBy = "foo", cascade=CascadeType.ALL)
private List<FooBar> foos;
}
#Entity
#Table(name="FOO_BAR")
public class FooBar{
private UUID fooBarId;
#ManyToOne
#JoinColumn(name = "fooId")
private Foo foo;
#ManyToOne
#JoinColumn(name = "barId")
private Bar bar;
//You can store other objects/fields on this table here.
}
You need to create an explicit UserServices entity and setup the relationship to the Ratings entity per your needs.
Remember that in hibernate you model relationships between entities (i.e. your java objects), not db tables.

Categories

Resources