JPA - Can an #JoinColumn be an #Id as well? SerializationException occurs - java

I am trying to use an #JoinColumn as an #Id using JPA and I am getting SerializationExceptions, "Could not serialize."
UserRole.java:
#Entity
#Table(name = "authorities")
public class UserRole implements Serializable {
#Column(name = "authority")
private String role;
#Id
#ManyToOne
#JoinColumn(name = "username")
private User owner;
...
}
User.java:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue
protected Long id;
#Column(name = "username")
protected String email;
#OneToMany(mappedBy = "owner", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
protected Set<UserRole> roles = new HashSet<UserRole>();
....
}
"username" is set up as a unique index in my Users table but not as the primary key.
Is there any way to make "username" act as the ID for UserRole? I don't want to introduce a numeric key in UserRole. Have I totally lost the plot here?
I am using MySQL and Hibernate under the hood.

That mapping doesn't really make sense. ID has to be unique, but ManyToOne says 'lots of these have the same User.'

Related

JPA/Hibernate/SpringBoot: Inserting #OneToMany relationship entities

I'm struggling with inserting #OneToMany entities in the JPA-Hibernate setup.
There are two associated tables with one of the table having the foreign key as the primary key of the source table.
employee
- id (PK)
employee_location
- employee_id (FK to employee)
Here are my entities:
Employee
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue()
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id")
private List<EmployeeLocation> employeeLocations;
}
Employee Location
#Entity(name = "employee_location")
class EmployeeLocation {
#Id
#Column(name = "employee_id")
private Long employeeId;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id", insertable = false, updatable = false)
private Employee employee;
}
Saving the entities:
List<EmployeeLocation> locations = Arrays.asList(new EmployeeLocation(), new EmployeeLocation());
Employee employee = new Employee();
employee.setLocations(locations);
employee.save(); // Throws exceptions
Which throws me this error:
org.springframework.orm.jpa.JpaSystemException: ids for this class must be manually assigned before calling save():
I tried changing #Entity to #Embeddable and removed the #Id on EmployeeLocation, but it gave me other Unmapped entity exceptions.
How do I handle inserting/updating #OneToMany entities? Is this possible?
How do I handle inserting/updating #OneToMany entities? Is this possible?
If you want the DB to generate the primary key values for you, you need to ask for it by using the #GeneratedValue annotation
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue // mean -> "Hey, DB, give me an ID"!
private Long id;
Same applies for EmployeeLocation
More details can be found here
If this does not fully solve your problem, leave a comment.
In your EmployeeLocation entity (detail) you cannot have as primary key the master's primary key, it needs its own. As follows:
#Entity(name = "employee_location")
class EmployeeLocation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "employee_location_id")
private Integer id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id", insertable = false, updatable = false)
private Employee employee;
}
In the case of Employee entity having a OneToOne relationship with EmployeeLocation entity you can use just #MapsId. This way, the EmployeeLocation id property is populated with the identifier of the post association.
class EmployeeLocation {
#Id
#Column(name = "id")
private Long id;
#OneToOne
#MapsId
private Employee employee;
}
but since your Employee entity has an OneToMany relationship with EmployeeLocation, Employee id property value can't be used as EmployeeLocation id property value because two or more EmployeeLocation entities asociated to the same Employee entity will have the same id value.
You'll need something like this:
#Entity
public class EmployeeLocation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#ManyToOne Employee employee;
}
In both cases you need to bind the EmployeeLocation entity to Employee entity, for example:
class Employee {
....
public void addLocation(EmployeeLocation location) {
location.setEmployee(this);
this.employeeLocations.add(location);
}
public void setLocations(List<EmployeeLocation> locations) {
for (EmployeeLocation location : locations) {
location.setEmployee(this);
}
this.employeeLocations = locations;
}
....
}
ANOTHER OPTION: Using ElementCollection
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue
private Long id;
#ElementCollection
#CollectionTable(
name="employee_location",
joinColumns=#JoinColumn(name="EMPLOYEE_ID"))
private Set<EmployeeLocation> employeeLocations;
}
#Embeddable
class EmployeeLocation {
// properties
}

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.

#ManyToOne #OneToMany Mapping , foreign key is null

#Entity
#Data
#NoArgsConstructor
public class Offer {
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
}
#Data
#EqualsAndHashCode
#Entity
#NoArgsConstructor
public class User {
#OneToMany(mappedBy = "user",cascade = CascadeType.ALL,fetch=FetchType.LAZY)
private Set<Offer> offers = new HashSet<Offer>();
}
Please help if the mapping is correct in table User and Offer .user_id column have null values ....:(
I'm not sure if these are only parts of the entities but in order for the entity to have an id, you need to provide it with one and annotate the relevant field as #Id.
I also use #GeneratedValue(strategy = GenerationType.IDENTITY) so each table will get it's own id (generated by Hibernate, you don't provide the id when you save a new entity and not a global id, otherwise let's say you add an Offer, you get id with value x, then add new User you get id with value x+1 and so on...
#Entity
#Data
#NoArgsConstructor
public class Offer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
}
#Data
#EqualsAndHashCode
#Entity
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy = "user",cascade = CascadeType.ALL,fetch=FetchType.LAZY)
private Set<Offer> offers = new HashSet<Offer>();
}`

hibernate one to many mapping with annotations - getting same values in the list

I am Linking User table with the Application Access. Here one User can have access to many applications.
I have done the mapping successfully with the below piece of code.
User entity object:
#Entity
#Table(name = "USER_TBL", uniqueConstraints = { #UniqueConstraint(columnNames = "USER_NAME") })
public class User implements Serializable {
.....
#Id
#GeneratedValue
#Column(name = "USER_ID", unique = true, nullable = false)
private Integer userId;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<UserAppAssociation> userAssociatedApplications = new ArrayList<UserAppAssociation>();
Getter and setter for userAssociatedApplications
}
Application access object:
#Entity
#Table(name="APPLICATION_ASSOC_TBL")
public class UserAppAssociation implements Serializable{
#Id
#Column(name="user_id", unique=true, nullable=false)
private Integer userId;
#Column(name = "application_id")
private Integer appId;
#Column(name = "user_type_id")
private Integer userTypeId;
...
#ManyToOne
#JoinColumn(name="USER_ID",insertable=false,updatable=false)
private User user;
..
getters and setters
}
Issue:
I am getting the same values in the Application List ('userAssociatedApplications'). Though i have different values in the application access table, I get the same values in the list. The first row value is repeated in the list.
DB:
I have 'User' table and the mapping is with application access
User table: USER_TBL
Columns
user_id name phone
Application access table : APPLICATION_ASSOC_TBL
Columns
User_id application_id and User_type
Note - no primary key in this table
Sample data:
User_id application_id User_type
1 1 1
1 2 1
1 3 1
Issue: I am getting the first value 1,1,1 in the list thrice.
Expected: List should be with 3 different values
Kindly help. I am not sure whether i am missing anyting in the annotation mapping.
Looks like a problem with this
#Id
#Column(name="user_id", unique=true, nullable=false)
private Integer userId;
#ManyToOne
#JoinColumn(name="USER_ID",insertable=false,updatable=false)
private User user;
Try to use this mapping. Please, refer this as a guide for names and don't use unnecessary annotations
#Entity
#Table(name = "xxx_users", uniqueConstraints = { #UniqueConstraint(columnNames = "f_name") })
public class User {
#Id
#GeneratedValue
#Column(name = "f_id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<UserAppAssociation> applications = new ArrayList<UserAppAssociation>();
}
#Entity
#Table(name="xxx_user_applications")
public class UserAppAssociation {
#Id
#GeneratedValue
#Column(name = "f_id")
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="fk_user")
private User user;
}

Categories

Resources