JPA Fetch Types - spring-boot

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;
}

Related

Add extra custom column to auto mapped Table in Spring JPA ManyToMany

#Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Table(name = "my_users")
public class MyUsers {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false)
private Long id;
#Column(nullable = false, unique = true)
private String userName;
private String password;
#ManyToMany
private List<MyUsers> connections;
}
This is my MyUsers Model Class. I am using Hibernate and MySQL.
#ManyToMany
private List<MyUsers> connections;
This ManyToMany relationship is automatically creating the table 'my_users_connections' with 'my_users_id' and 'connections_id' colums. How can I add extra columns to this auto mapped table?
It's not ideal solution...
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "my_users")
public class MyUsers implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false)
private Long myUsersId;
#Column(nullable = false, unique = false)
private String userName;
private String password;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "my_users_connections",
joinColumns = { #JoinColumn(name = "my_users_id") },
inverseJoinColumns = { #JoinColumn(name = "connections_id") })
private List<MyUsers> connections;
}
Create embedded id MyUsersConnectionsPK:
#Data
#Embeddable
public class MyUsersConnectionsPK implements Serializable {
#Column(name = "my_users_id")
private Long myUsersId;
#Column(name = "connections_id")
private Long connectionsId;
}
Create MyUsersConnections, which represent ManyToMany
#Data
#Entity
#Table(name = "my_users_connections")
public class MyUsersConnections implements Serializable {
#EmbeddedId
private MyUsersConnectionsPK id;
#ManyToOne
#MapsId("my_users_id")
#JoinColumn(name = "my_users_id")
private MyUsers myUsersId;
#ManyToOne
#MapsId("connections_id")
#JoinColumn(name = "connections_id")
private MyUsers connectionsId;
#Column(name = "extra_column")
private String extraColumn;
}
Create JPA repository
#Repository
public interface MyUsersConnectionsRepository extends JpaRepository<MyUsersConnections, MyUsersConnectionsPK> {
List<MyUsersConnections> findMyUsersConnectionsByMyUsersIdMyUsersId(Long id);
}
And simple sample for using:
#Service
public class Test {
#Autowired
private MyUsersConnectionsRepository myUsersConnectionsRepository;
#Autowired
private MyUsersRepository myUsersRepository;
public void test() {
MyUsers myUsers = new MyUsers();
myUsers.setUserName("user name");
myUsers.setPassword("password");
MyUsers myUsers2 = new MyUsers();
myUsers2.setUserName("user name 2");
myUsers2.setPassword("password 2");
myUsers.setConnections(Collections.singletonList(myUsers2));
myUsers = myUsersRepository.saveAndFlush(myUsers);
List<MyUsersConnections> myUsersConnections = myUsersConnectionsRepository.findMyUsersConnectionsByMyUsersIdMyUsersId(myUsers.getMyUsersId());
MyUsersConnections item = myUsersConnections.get(0);
item.setExtraColumn("Extra column");
myUsersConnectionsRepository.saveAndFlush(item);
}
}

(Do not display relationship values)

I have two entity with name of the article and article Category.
and they have one-to-many relationships.
I use #JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class,property = "id")
but I cant see data of article category(category_id) in spring data rest.
ArticleCategory.class
#Entity
#Table(name = "article_category")
#Getter
#Setter
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "articleCategory", fetch = FetchType.LAZY)
private Set<Article> articles = new HashSet<>();
}
Article.class
#Entity
#Table(name = "article")
#Getter
#Setter
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Article implements Serializable {
public Article() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private ArticleCategory articleCategory;
#Column(name = "title")
private String title;
#Column(name = "image_url")
private String image_url;
#Column(name = "short_description")
private String short_description;
#Column(name = "text")
private String text;
#Column(name = "keywords", nullable = true)
private String keywords;
#Column(name = "visit", nullable = false)
private int visit;
#Column(name = "code", nullable = false)
private UUID code;
#Column(name = "date_created")
#CreationTimestamp
private Date dateCreated;
#Column(name = "date_updated", nullable = false)
#UpdateTimestamp
private Date dateUpdated;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
public Article(String title, String image_url, String short_description, String text, String keywords, int visit, UUID code) {
this.title = title;
this.image_url = image_url;
this.short_description = short_description;
this.text = text;
this.keywords = keywords;
this.visit = visit;
this.code = code;
}
}
Article Repository
#CrossOrigin("http://localhost:4200")
#RepositoryRestResource(collectionResourceRel = "article", path = "article")
public interface ArticleRepository extends JpaRepository<Article,Long> {
Article findByCode(UUID uuid);
}
And this is output of spring data rest
enter image description here
That is exactly because you used #JsonManagedReference and #JsonBackReference. Keep in mind the following when using them:
#JsonManagedReference is the forward part of the relationship and is the one that gets serialized normally.
#JsonBackReference is the back part of the relationship and it will be omitted from serialization.
The serialized Article object does not contain a reference to the ArticleCategory object.
If you want to have any ArticleCategory data when serializing Article you can either use #JsonIdentityInfo so that one of the properties is serialized (in this case I've chosen id for both):
#Entity
#Table(name = "article")
#Getter
#Setter
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Article implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private ArticleCategory articleCategory;
}
#Entity
#Table(name = "article_category")
#Getter
#Setter
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "articleCategory" ,fetch = FetchType.LAZY)
private Set<Article> articles=new HashSet<>();
}
If you are only interested in categoryId another possibility would be to use #JsonIgnore on private Set<Article> articles property so that it is not serialized:
#Entity
#Table(name = "article_category")
#Getter
#Setter
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL,mappedBy = "articleCategory" ,fetch = FetchType.LAZY)
private Set<Article> articles=new HashSet<>();
}
If none of those suits your needs you might need to implement your own custom serializer. You can read more about all those options at https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion.
I solved the problem using the controller
And that's why #JsonManageRefrence and #JsonBackRefrence do not work
I replaced the lazy load with the eager load in both entity
#ManyToOne(fetch = FetchType.Eager)
#JoinColumn(name = "user_id")
#JsonManageRefrence
private User user;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "articleCategory",
fetch = FetchType.Eager)
#JsonBackRefrence
private Set<Article> articles = new HashSet<>();
and then add a controller
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
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("/getAllArticle")
public class MyController {
private ArticleRepository articleRepository;
// you must do constructor injection
#GetMapping("/getAllArticle")
public List<Article> allArticle()
{
return articleRepository.findAll();
}
}

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

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