how to insert data in one to many relationship in spring jpa - spring

I am new in Spring. I want to insert the data into the AUDIT table. There is a one-two-many relationship between USER and AUDIT tables.
For every login and logout data should be inserted into the AUDIT table. How can I do that?
The user entity:
#Table(name="USER")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="USERID")
private Long id;
private String username;
private String email;
private Long phone;
private String password;
private int OTP;
private boolean activation_flag;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="USERID")
private Set<Audit> audit;
#ManyToOne
#JoinColumn(name="ADDRESSID")
private Address address;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "USER_AUTHORITY",
joinColumns = {#JoinColumn(name = "USER_ID", referencedColumnName = "USERID")},
inverseJoinColumns = {#JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")})
private List<Authority> authorities;
The audit entity:
#Entity
#Table(name="AUDIT")
public class Audit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="AUDITID")
private long auditid;
#Column(name="GEOLOCATION")
private String geolocation;
#Column(name="ACTION")
private String action;
#Column(name="DATETIME")
private Date datetime;
#Column(name="DEVICEID")
private long deviceid;
#Column(name="ACTIONSUCCESS_FAIL")
private boolean actionsuccess_fail;
#Column(name="JWT_TOKEN")
#Type(type="text")
private String JWT_token;
#ManyToOne
private User user;
The audit repository:
public interface AuditRepository extends JpaRepository<Audit, Long> {
}
The user repository:
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
User findByEmail(String email);
User findByPhone(Long phone);
User findById(Long id);
}

Add the audit to the user you want the audit associated with, then save.

Related

JPA OneToOne and shared primary key need manual assignment

I'm using Springboot and JPA to create two tables sharing the same primary key.
For the first table I write:
public class UserAccount implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE,
CascadeType.REFRESH}, fetch=FetchType.LAZY)
#PrimaryKeyJoinColumn
private UserLogin login;
}
For the second table I write:
public class UserLogin implements Serializable
{
#Id
private Long user_id;
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},
fetch=FetchType.LAZY)
#MapsId("user_id")
#JoinColumn(name = "user_id", referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private UserAccount user;
public void setUser(UserAccount user)
{
this.user = user;
this.user_id = user.getId();
}
}
Other stuff are omitted for conciseness. The code works because I manually set the id of UserLogin by writing the statement
this.user_id = user.getId();
otherwise I get the error:
Hibernate error: ids for this class must be manually assigned before calling save():
I guess that the ids can be manually managed but I cannot get the right configuration.
UPDATE:
I found the solution thanks (see the accepted answer). Now I just would get rid of the findById() when setting the user login.
//these methods are defined within a dedicated #Service
#Transactional
public void createLoginInfo(UserAccount user)
{
UserLogin userlogin=new UserLogin();
this.addLoginToUser(userlogin,user);
loginService.save(userlogin);
}
#Transactional
public void addLoginToUser(UserLogin login, UserAccount account)
{
//whit this commented line works
//UserAccount acc= this.findById(account.getId());
login.setUser(account);
account.setLogin(login);
}
//In a transactional test method I first create the user then I call
userService.save(theuser);
userService.createLoginInfo(theuser);
You have a bidirectional relationship, but have mapped it with a few competing options that don't work well together. First, in UserAccount, it isn't clear why you have an ID that is generated, yet try to also map it with the relationship (specifically using a PrimaryKeyJoinColumn). If you want it generated, it can't also be a foreign key value in a reference - and you've already got this relationship setup as the 'other' side via the 'mappedBy' setting. It should just be:
public class UserAccount implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE,
CascadeType.REFRESH}, fetch=FetchType.LAZY)
private UserLogin login;
}
User login then should just be:
public class UserLogin implements Serializable {
#Id
private Long user_id;
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},
fetch=FetchType.LAZY)
#MapsId("user_id")
#JoinColumn(name = "user_id", referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private UserAccount user;
public void setUser(UserAccount user) {
this.user = user;
}
}
Note because you have the mapsId annotation on the user relationship, JPA will set the user_id property for you once the ID is assigned - there is no need to manually set it yourself. You can, but if you do you must insure it was assigned previously - which requires a save/flush on the UserAccount. If you don't actually use the Long user_id property, you don't really even need to map it; you can just mark the user property as the ID:
public class UserLogin implements Serializable {
#Id
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},
fetch=FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private UserAccount user;
public void setUser(UserAccount user) {
this.user = user;
}
}
The Long ID from UserAccount then can be used to lookup UesrAccounts and UserLogin instances.
Try this :
public class UserLogin implements Serializable
{
#Id
private Long user_id;
#OneToOne(fetch=FetchType.LAZY)
#MapsId
#JoinColumn(name = "user_id")
private UserAccount user;
public UserAccount getUser() {
return user;
}
public void setUser(UserAccount user) {
this.user = user;
}
}
public class UserAccount implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
To persist UserLogin :
EntityManager em;
UserAccount user = em.find(UserAccount.class, 1L)
UserLogin login = new UserLogin();
login.setUser(user);
em.persist(login);

OneToOne CascadeType in spring data jpa

I use OneToOne in the spring data JPA and I want to delete a record from the Address table without touching the user. But I can't.
If I remove User, in this case Address is removed, that's good.
But how can you delete an Address without touching the User?
https://github.com/myTestPercon/TestCascade
User.Java
#Entity
#Table(name = "user", schema = "testCascade")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private Address address;
// Getter and Setter ...
}
Address.java
#Entity
#Table(name = "address", schema = "testCascade")
public class Address implements Serializable {
#Id
private Long id;
#Column(name = "city")
private String city;
#OneToOne
#MapsId
#JoinColumn(name = "id")
private User user;
// Getter and Setter ...
}
DeleteController.java
#Controller
public class DeleteController {
#Autowired
ServiceJpa serviceJpa;
#GetMapping(value = "/deleteAddressById")
public String deleteAddressById () {
serviceJpa.deleteAddressById(4L);
return "redirect:/home";
}
}
You got your mapping wrong thats all is the problem .
try the below and see
User.java
#Entity
#Table(name = "user", schema = "testCascade")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="foriegn key column in user table for address example.. address_id")
private Address address;
// Getter and Setter ...
}
Address.java
#Entity
#Table(name = "address", schema = "testCascade")
public class Address implements Serializable {
#Id
private Long id;
#Column(name = "city")
private String city;
//name of the address variable in your user class
#OneToOne(mappedBy="address",
cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH})
private User user;
// Getter and Setter ...
}
In order to solve this problem, you need to read the hibernate Documentation Hibernate Example 162, Example 163, Example 164.
And also I recommend to look at this is Using #PrimaryKeyJoinColumn annotation in spring data jpa
This helped me in solving this problem.
And also you need to specify the parameter orphanRemoval = true
User.java
#Entity(name = "User")
#Table(name = "user", schema = "testother")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private Address address;
public void addAddress(Address address) {
address.setUser( this );
this.address = address;
}
public void removeAddress() {
if ( address != null ) {
address.setUser( null );
this.address = null;
}
}
// Getter and Setter
}
Address.java
#Entity(name = "Address")
#Table(name = "address", schema = "testother")
public class Address implements Serializable {
#Id
private Long id;
#Column(name = "city")
private String city;
#OneToOne
#MapsId
#JoinColumn(name = "id")
private User user;
// Getter and Setter
}
DeleteController .java
#Controller
public class DeleteController {
#Autowired
ServiceJpa serviceJpa;
#GetMapping(value = "/deleteUser")
public String deleteUser () {
User user = serviceJpa.findUserById(2L).get();
user.removeAddress();
serviceJpa.saveUser(user);
return "/deleteUser";
}
}
Or make a custom SQL query.
#Repository
public interface DeleteAddress extends JpaRepository<Address, Long> {
#Modifying
#Query("delete from Address b where b.id=:id")
void deleteBooks(#Param("id") Long id);
}
public class Address {
#Id
private Long id;
#MapsId
#JoinColumn(name = "id")
private User user;
}
Rename #JoinColumn(name = "id") to #JoinColumn(name = "user_id")
You can't say that the column that will point to user will be the id of the Address

JPA Fetch Types

I have two model classed called users and authorities. I used fetch type EAGER so that all the data even the ones from the authorities tables would be fetched as they are mapped to each other, But for some reason it is not fetching the details from the authorities table.
#Getter
#Setter
#Entity
#Table
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(unique = true)
private String email;
private String firstname;
private String lastname;
private String password;
#OneToMany(mappedBy = "users",
cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER,
targetEntity = Authorities.class)
private Set<Authorities> authorities;
}
#Getter
#Setter
#Entity
#Table
public class Authorities {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String role;
#ManyToOne(targetEntity = Users.class,
cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER)
#JoinColumn(name = "authorities")
private Users users;
}
#Repository
public interface UserRepository extends JpaRepository<Users, Integer> {
Optional<Users> findByEmail(String email);
}
This method findByEmail is not fetching all the details from the database. It is only fetching the data from the users table but not from the authorities table even though i am using fetch type EAGER.
I think that Spring Data ignores fetch types and fetch mode. You can use Entity Graphs. See documentation
#Getter
#Setter
#Entity
#Table
#NamedEntityGraph(name = "authorities.detail", attributeNodes = NamedAttributeNode("authorities"))
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(unique = true)
private String email;
private String firstname;
private String lastname;
private String password;
#OneToMany(mappedBy = "users",
cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER,
targetEntity = Authorities.class)
private Set<Authorities> authorities;
}

Why I can't delete data in cascade way?

The problem is when I want to delete user I'm getting error in Spring Boot like that:
java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (32506632_pam.badge, CONSTRAINT FK4aamfo6o0h5ejqjn40fv40jdw FOREIGN KEY (user_id) REFERENCES user (id))
I'm guessing that I need to delete data in cascade way. So I've placed CascadeType.REMOVE value to #OneToOne annotation like that, but it doesn't work:
badge entity
#Entity
#Data
#Table(name = "badge")
#AllArgsConstructor
#NoArgsConstructor
public class Badge {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonManagedReference
#ManyToMany(mappedBy = "badges", fetch = FetchType.LAZY)
private List<Reader> readers;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval=true)
#JoinColumn(name = "user_id")
private User user;
private String number;
#Lob
#Basic(fetch = FetchType.LAZY)
private byte[] photo;
}
user entity
#Entity
#Data
#Table(name = "user")
#AllArgsConstructor
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String lastname;
private String pesel;
private String email;
private String telephone;
private Integer age;
private String gender;
}
reader entity
#Entity
#Data
#Table(name = "reader")
#AllArgsConstructor
#NoArgsConstructor
public class Reader {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonBackReference
#ManyToMany(fetch = FetchType.LAZY)
private List<Badge> badges;
private String department;
private String room;
private Boolean status;
}
Class which loads initial data
#Component
public class DataLoader implements ApplicationRunner {
#Autowired
private UserService userService;
#Autowired
private BadgeService badgeService;
#Autowired
private ReaderService readerService;
#Override
public void run(ApplicationArguments args) throws Exception {
User user1 = new User(null, "Jan", "Kowal", "11111111111", "jan#kowal.pl", "+48111111111", new Integer(23), "male");
userService.saveUser(user1);
Reader reader1 = new Reader(null, null, "Warehouse", "207A", new Boolean("true"));
Badge badge1 = new Badge(null, Arrays.asList(reader1), user1, "738604289120", null);
badgeService.saveBadge(badge1);
reader1.setBadges(Arrays.asList(badge1));
readerService.saveReader(reader1);
}
}
Endpoint for deleting user - it uses repository which extends CrudRepository and uses default delete behavior.
#DeleteMapping("/deleteUserById/{id}")
private void deleteUserById(#PathVariable Long id) {
userService.deleteUserById(id);
}
Database structure in phpmyadmin
My goal is to delete user and associated badge with him, then to delete row in reader_badges table.

How to configure oneToMany Mapping in User -Role-Permission

I have a spring boot app(Spring boot 2) with spring data jpa.i have 3 MYSQL tables to store user information ,roles and permissions
User will contains basic user details like username ,password firstname,lastname.
Role represent the user roles like Admin ,User,Staff,test (user can have many roles)
Permission has 3 possibilities Read,Write,Customize(each role has many permissions)
UserRole - joint table for User and Roles
RolePermission - joint table for roles and permissions
Am looking for Spring boot service , which will return false if the user is not validated against the User table ,if the user is validated successfully then the response should contains the user roles and permissions .
I was able to build the sample rest service but failed to setup the below
How to configure the entity classes for the oneToMany mapping in this requirement
What would be the corresponding query in the repository interface
Please see the entity tables
#Entity
#Table( name = "TURBINE_USER" )
public class PortalUser {
public PortalUser() {
}
public PortalUser(long userID ,String userName , String password , String firstName, String lastName, String email) {
this.userID = userID;
this.userName = userName;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
#Id
#Column(name="USER_ID",unique=true)
private long userID;
#NotNull
#Column(name="LOGIN_NAME",unique=true)
private String userName;
#NotNull
#Column(name="PASSWORD_VALUE")
private String password;
#NotNull
#ColumnDefault("")
#Column(name="FIRST_NAME")
private String firstName;
#NotNull
#ColumnDefault("")
#Column(name="LAST_NAME")
private String lastName;
#Column(name="EMAIL")
private String email;
#Column(name="CONFIRM_VALUE")
private String confirmValue;
#NotNull
#Column(name="CREATED")
private Timestamp createdDt;
#NotNull
#Column(name="MODIFIED")
private Timestamp modified;
#NotNull
#Column(name="LAST_LOGIN")
private Timestamp lastLogin;
#Column(name="DISABLED")
private char disabled;
#Column(name="OBJECTDATA")
private byte[] objectData;
#NotNull
#Column(name="PASSWORD_CHANGED")
private Timestamp passwordChanged;
// getters and setters
}
#Entity
#Table(name = "TURBINE_ROLE")
public class Role {
#Id
#Column(name= "ROLE_ID",unique = true)
private long roleId;
#Column(name= "ROLE_NAME")
private String roleName;
}
#Entity
#Table(name ="TURBINE_PERMISSION")
public class Permission {
#Id
#Column(name= "PERMISSION_ID")
private long permissionId;
#Column(name= "PERMISSION_NAME")
private String name;
}
#Entity
#Table(name= "TURBINE_USER_GROUP_ROLE")
public class UserRoles {
#Column(name="USER_ID")
private PortalUser user;
#Column(name="ROLE_ID")
private Role roles;
}
#Entity
#Table(name="TURBINE_ROLE_PERMISSION")
public class RolePermission {
#Column(name= "ROLE_ID")
private Role roleId;
#Column(name= "PERMISSIONID")
private Permission permissionId;
}
It is ManyToMany relationship between User and Role, Role and Permission entities. You should not define Join tables as entities, and they are generated/managed by JPA implementation.
For example, ManyToMany annotation for User and Role entities:
public class PortalUser {
...
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "User_Role",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")})
private Set<Role> roles;
}
Tested with CMobileCom JPA.
Disclaimer: I am a developer of CMobileCom JPA, a light weight JPA implementation for Java and Android.
One of the simple way to define the relation among User-Role-Permission is as bellow - Define entities class for Role and Permission and declare the many to many relation inside User entity, Which auto created the two link table user_role and user_permission.
1. Role Entity Class
#Entity
#Table(name = "ROLE")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
.
.
.
}
2. Permission Entity Class
#Entity
#Table(name = "PERMISSION")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Permission {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
.
.
.
}
3. User Entity Class
#Entity
#Table(name = "USER")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
.
.
.
// Relation with role
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinTable(name="USER_ROLE",
joinColumns = {#JoinColumn (name="USER_ID", referencedColumnName="id")},
inverseJoinColumns = {#JoinColumn(name="ROLE_ID", referencedColumnName="id")}
)
private List<Role> roles;
// Relation with permission
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinTable(name="USER_PERMISSION",
joinColumns = {#JoinColumn (name="USER_ID", referencedColumnName="id")},
inverseJoinColumns = {#JoinColumn(name="PERMISSION_ID", referencedColumnName="id")}
)
private List<Permission> permissions;
}

Resources