Load single entity without relations jpa - java

I have two entities students and grades. There is a one to many relationship between them. But when I make an api call to get students, I get all grades with them. Is there a way to load only student entity ? I tried FetchType.LAZY but it did not work.
Student model:
#Entity
#Table
public class Student {
#Id
#GeneratedValue(
strategy= GenerationType.AUTO,
generator="native"
)
#GenericGenerator(
name = "native",
strategy = "native"
)
private Long id;
#NotBlank(message = "Name cannot be null")
private String name;
#NotBlank(message = "Lastname cannot be null")
private String lastname;
#NotNull(message = "Age cannot be null")
private int age;
#NotBlank(message = "Email cannot be null")
private String email;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "student")
private Set<Grade> grades = new HashSet();
}
Grade model:
#Entity
#Table
public class Grade {
#Id
#GeneratedValue(
strategy= GenerationType.AUTO,
generator="native"
)
#GenericGenerator(
name = "native",
strategy = "native"
)
private Long id;
private String subject;
private double value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "student_id", nullable = false)
private Student student;
Student service :
#Service
public class StudentService {
private final IStudentRepository studentRepository;
public StudentService(IStudentRepository studentRepository){
this.studentRepository = studentRepository;
}
public List<Student> GetAll(){
return studentRepository.findAll();
}
Hibernate output:
Hibernate: select student0_.id as id1_1_, student0_.age as age2_1_, student0_.email as email3_1_, student0_.lastname as lastname4_1_, student0_.name as name5_1_ from student student0_
Hibernate: select grades0_.student_id as student_4_0_0_, grades0_.id as id1_0_0_, grades0_.id as id1_0_1_, grades0_.student_id as student_4_0_1_, grades0_.subject as subject2_0_1_, grades0_.value as value3_0_1_ from grade grades0_ where grades0_.student_id=?

To load only the student entity, you can create a separate projection like StudenDTO and use it to pass across repo-service-controller.
The relevant part of projection is here
interface StudentDTO {
String getName();
String getLastname();
Integer getAge();
String getEmail();
}
Now when you hit localhost:8080/students/create-dto-return?id=1
You won't see the next query being fired and this is triggered jackson during serialization.
Entire code is as below:
package com.example.demo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.HashSet;
import java.util.Set;
#RestController
#RequestMapping("/students")
public class StudentsController {
private final StudentService studentService;
#Autowired
public StudentsController(StudentService studentService) {
this.studentService = studentService;
}
#GetMapping
public Iterable<Student> list() {
return studentService.list();
}
#PostMapping
public Student createEntityReturn(#RequestBody Student student) {
return studentService.save(student);
}
#GetMapping(value = "/create-dto-return")
public StudentDTO getByDto(#RequestParam("id") Integer id) {
return studentService.findStudentOnlyByIdDto(id);
}
#GetMapping(value = "/create-entity-return")
public Student getById(#RequestParam("id") Integer id) {
return studentService.findStudentById(id);
}
}
#Getter
#Setter
#ToString
#Entity
class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotBlank(message = "Name cannot be null")
private String name;
#NotBlank(message = "Lastname cannot be null")
private String lastname;
#NotNull(message = "Age cannot be null")
private int age;
#NotBlank(message = "Email cannot be null")
private String email;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "student")
private Set<Grade> grades = new HashSet<>();
}
#Getter
#Setter
#Entity
#Table
class Grade {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String subject;
private double value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "student_id", nullable = false)
private Student student;
}
#Service
class StudentService {
private final StudentRepository studentRepository;
#Autowired
StudentService(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
#Transactional
public Student save(Student student) {
return studentRepository.save(student);
}
#Transactional(readOnly = true)
public Iterable<Student> list() {
return studentRepository.findAll();
}
#Transactional(readOnly = true)
public StudentDTO findStudentOnlyByIdDto(Integer id) {
return studentRepository.findStudentById(id);
}
#Transactional(readOnly = true)
public Student findStudentById(Integer id) {
return studentRepository.findById(id).orElseThrow(() -> new RuntimeException("Unable to find student"));
}
}
interface StudentDTO {
String getName();
String getLastname();
Integer getAge();
String getEmail();
}
#Repository
interface StudentRepository extends JpaRepository<Student, Integer> {
StudentDTO findStudentById(Integer id);
}

Related

How can I filter out a get request of all users in my DB so that users with an admin role are not shown in the list?

I am building a spring boot application that allows an admin to view a list of all users saved in my Database. However, at the moment, all users including the ones with an admin role are being displayed. I was suggested to filter out admins in the backend code but I am not sure how to go about doing this. If anyone could help me, it would very much appreciated. Thank you! (I am using JPA + Hibernate)
UPDATE:
I was suggested a solution that was very useful below. I needed to make a getUsers method which queries data from my Database in a way that filters out admins. At first, I was having issues with the SQL part of the query since I am using hibernate (I think, correct me if I am wrong). I was made aware that you can't use a JPQL query to access raw entity data from tables, since I was using #JoinTable to create my user_roles table that doesn't have an entity class for it, I was confused. Seen in my User class as follows:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
I was receiving a red line in my user repository class at user_role ur in FROM User u,Role r, user_role ur. Futhermore, I was receiving the following error: user_role is not mapped [SELECT u.id,u.username,u.email FROM com.Application.models.User u,com.Application.models.Role r, user_role ur WHERE u.id=ur.id AND r.id=ur.id AND r.name<>'ADMIN']. My Role entity is used to map the role ID and the role name whereas my user_role table contains a column of the user ID and the role ID in one table for mapping the user id to a role id. That is where my last error was.
My Solution:
#Query("SELECT u FROM User u join u.roles r WHERE NOT EXISTS" + "(SELECT r FROM u.roles WHERE r.name = 'ROLE_ADMIN')")
List<User> getUsers();
This succesfuly filtered out users with an admin role in my case. I hope this helps someone one day, I was stuck on this for a minute. Thanks for all the help!
User.java:
package com.Application.models;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
#Entity
#Table( name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username"),
})
public class User {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "username")
#NotBlank
#Size(max = 20)
private String username;
#Column(name = "email")
#NotBlank
#Size(max = 50)
#Email
private String email;
#NotBlank
#Size(max = 120)
private String password;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "user")
private Set<UserPost> userPosts = new HashSet<>();
public User(String username, String email
,String password) {
this.username = username;
this.email = email;
this.password = password;
}
public User() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
TestController.java:
package com.Application.controller;
import com.Application.models.User;
import com.Application.repository.UserPostsRepository;
import com.Application.repository.UserProfileRepository;
import com.Application.repository.UserRepository;
import com.Application.security.jwt.JwtUtils;
import com.Application.security.services.UserDetailsService;
import com.Application.security.services.UserPostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CrossOrigin;
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;
#CrossOrigin(origins = "http://localhost:3000")
#RestController
#RequestMapping("/api/test")
public class TestController {
private static final String AUTH_HEADER = "authorization";
#Autowired
private final UserPostsRepository userPostRepo;
#Autowired
private final UserRepository userRepo;
#Autowired
private final UserProfileRepository userProfRepo;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private UserPostService userPostService;
#Autowired
JwtUtils jwtUtils;
public TestController(UserPostsRepository userPostRepo, UserRepository userRepo, UserProfileRepository userProfRepo) {
this.userPostRepo = userPostRepo;
this.userRepo = userRepo;
this.userProfRepo = userProfRepo;
}
#GetMapping("/all")
public String allAccess() {
return "Public Content.";
}
#GetMapping("/user")
#PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")
public String userAccess() {
return "User Content.";
}
#GetMapping("/mod")
#PreAuthorize("hasRole('MODERATOR')")
public String moderatorAccess() {
return "Moderator Board.";
}
#GetMapping("/admin")
#PreAuthorize("hasRole('ADMIN')")
public List<User> adminAccess(Model model) {
List<User> allUsers = userRepo.getUsers();
allUsers.forEach(user -> model.addAttribute("username", user.getUsername()));
return allUsers;
}
}
Role.java:
package com.Application.models;
import javax.persistence.*;
#Entity
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Enumerated(EnumType.STRING)
#Column(length = 20)
private ERole name;
public Role() {
}
public Role(ERole name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public ERole getName() {
return name;
}
public void setName(ERole name) {
this.name = name;
}
}
ERole.java:
package com.Application.models;
public enum ERole {
ROLE_USER,
ROLE_MODERATOR,
ROLE_ADMIN
}
UserRepository.java:
package com.Application.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import com.Application.models.User;
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
User findUserById(Long id);
Optional<User> findByEmail(String email);
List<User> findAll();
#Query("SELECT u.id,u.username,u.email FROM User u,Role r, user_role ur WHERE u.id=ur.id AND r.id=ur.id AND r.name<>\'ADMIN\'") //Error at users u, roles r, user_roles ur even though the table names are right
List<User> getUsers();
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
RoleRepository.java:
package com.Application.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.Application.models.ERole;
import com.Application.models.Role;
#Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Optional<Role> findByName(ERole name);
}
There are better ways to do this but here is a simple way.Just add the below method in your repository interface
#Query("SELECT u.firstname,u.lastname,u.email FROM user u,role r,user_role ur WHERE u.id=ur.user_id AND r.id=ur.role_id AND r.name<>\'ADMIN\'")
List<User> getUsers();
getUsers() retuns a list of users except admin users.
Of course you can change the query depending on what columns are in user table and change user_roles with the name of the appropriate table in your database.

Mapping DTO inside DTO using ModelMapper

I have the next entities:
#Entity
#Data
#EqualsAndHashCode(callSuper = true)
public class Student extends BaseEntity {
private String identifier;
#NotNull
#Embedded
private UserInformation userInformation;
#Embeddable
#ToString
public class UserInformation {
#NotNull
#Size(min = 1, max = 40)
private String firstName;
#NotNull
#Size(min = 1, max = 40)
private String lastName;
#NotNull
private LocalDate dateOfBirth;
#OneToOne
private Address address;
#Enumerated(EnumType.STRING)
private Gender gender;
}
And I create a DTO for the Student class:
#Data
#ToString
public class CreateStudentDTO {
#NotNull
private UserInformationDTO userInformation;
}
#Data
#ToString
public class UserInformationDTO {
#NotNull
#Size(min = 1, max = 40)
private String firstName;
#NotNull
#Size(min = 1, max = 40)
private String lastName;
}
But because UserInformationDTO and UserInformation I did not find a solution to map them.
This is what I tried in the Controller:
#RestController
#RequestMapping("/student")
public class StudentController {
#Autowired
private StudentService studentService;
#Autowired
private ModelMapper modelMapper;
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public CreateStudentDTO createStudent(#RequestBody CreateStudentDTO studentDTO) {
System.out.println("dto: " + studentDTO);
TypeMap<CreateStudentDTO, Student> propertyMapper = modelMapper
.createTypeMap(CreateStudentDTO.class, Student.class);
propertyMapper.addMapping(CreateStudentDTO::getUserInformation, Student::setUserInformation);
Student student = modelMapper.map(studentDTO, Student.class);
System.out.println(student);
return null;
}
}
But the field in Student after mapping will be: userInformation=UserInformation(firstName=null, lastName=null, dateOfBirth=null, address=null, gender=null)
I want to map only the firstName and lastName for first, then I can map others if this works.

ManyToOne Lazy loading not working Spring boot

I have make the rest api using Spring boot. It has two entities employee and department. Employee has department object so relation is ManyToOne. It is unidirectional. Department do not have the employees list. When I hit the employees GET method endpoint I am getting com.fasterxml.jackson.databind.exc.InvalidDefinitionException Exception. Please tell me how to solve it.
Employee Class:
#Entity
#Table(name = "employees")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String firstName;
private String lastName;
private Integer salary;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="department_id",referencedColumnName = "id",nullable = false)
private Department department;
Department Class:
#Entity
#Table(name = "departments")
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
Employee Service:
#Service
public class EmployeeService {
private final EmployeeRepository employeeRepository;
#Autowired
public EmployeeService(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
public List<Employee> getAllEmployees(){
return employeeRepository.findAll();
}
public Employee getEmployeeById(Integer id){
Optional<Employee> optionalEmployee = employeeRepository.findById(id);
if(optionalEmployee.isEmpty()){
throw new IllegalStateException("Department does not exists");
}
return optionalEmployee.get();
}
public Employee createEmployee(Employee employee){
return employeeRepository.save(employee);
}
Employee Controller:
#RestController
#RequestMapping(path = "/employees")
public class EmployeeController {
private final EmployeeService employeeService;
#Autowired
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
#GetMapping
public List<Employee> getAllDepartments(){
return employeeService.getAllEmployees();
}
#PostMapping
public Employee createEmployee(#RequestBody Employee employee){
return employeeService.createEmployee(employee);
}}
You have to put #JsonProperty(access = JsonProperty.Access.WRITE_ONLY) annotation in ManyToOne relationship mapping

Unable to evaluate the expression Method threw 'org.hibernate.exception.GenericJDBCException' exception

I have 2 models in my application which have the one-to-many and many-to-one relationships.
The model classes are:
Invoice Model
#Entity
#Data
public class Invoice implements java.io.Serializable {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid2")
private String id;
private String business;
private String client;
private String invoiceNo;
#Enumerated(EnumType.STRING)
private InvoiceStatus status;
private String additionalInfo;
#OneToMany(mappedBy = "invoice")
private Set<InvoiceItem> items = new HashSet<>();
#CreationTimestamp
private LocalDateTime createdAt;
#UpdateTimestamp
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
}
InvoiceItem Model
#Entity
#Data
public class InvoiceItem {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid2")
private String id;
#ManyToOne
#JoinColumn(name="invoice_id", nullable = false)
private Invoice invoice;
private String description;
#Enumerated(EnumType.STRING)
private InvoiceItemType type;
#CreationTimestamp
private LocalDateTime createdAt;
#UpdateTimestamp
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
}
I have an api endpoint which is used to create an invoiceItem for an invoice
#RestController
#RequestMapping(path="/api/v1/invoices/{invoice}/items")
public class InvoiceItemController {
#Autowired
private ModelMapper modelMapper;
#Autowired
private InvoiceItemService invoiceItemService;
#Autowired
private InvoiceService invoiceService;
#PostMapping
public ResponseEntity<InvoiceItem> addInvoiceItem(#PathVariable("invoice") String invoiceId, #RequestBody InvoiceItemCreationDto invoiceItemCreationDto) throws NotFoundException {
Optional<Invoice> invoiceOptional = invoiceService.findInvoiceById(invoiceId);
if (!invoiceOptional.isPresent()) {
throw new NotFoundException("Invoice not found");
}
InvoiceItem invoiceItem = modelMapper.map(invoiceItemCreationDto, InvoiceItem.class);
invoiceItem.setInvoice(invoiceOptional.get());
InvoiceItem savedInvoiceItem = invoiceItemService.addInvoiceItem(invoiceItem);
return new ResponseEntity<>(savedInvoiceItem, HttpStatus.CREATED);
}
}
Invoice Service
package com.spencerfeng.invoiceservice.services;
import com.spencerfeng.invoiceservice.models.Invoice;
import com.spencerfeng.invoiceservice.repositories.InvoiceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Optional;
public class InvoiceServiceImpl implements InvoiceService {
#Autowired
InvoiceRepository invoiceRepository;
#Override
public Invoice addInvoice(Invoice invoice) {
return invoiceRepository.save(invoice);
}
#Override
public Optional<Invoice> findInvoiceById(String id) {
return invoiceRepository.findById(id);
}
}
Invoice Repository
package com.spencerfeng.invoiceservice.repositories;
import com.spencerfeng.invoiceservice.models.Invoice;
import org.springframework.data.repository.CrudRepository;
public interface InvoiceRepository extends CrudRepository<Invoice, String> {
}
InvoiceItem Service
package com.spencerfeng.invoiceservice.services;
import com.spencerfeng.invoiceservice.models.InvoiceItem;
import com.spencerfeng.invoiceservice.repositories.InvoiceItemRepository;
import org.springframework.beans.factory.annotation.Autowired;
public class InvoiceItemServiceImpl implements InvoiceItemService {
#Autowired
private InvoiceItemRepository invoiceItemRepository;
#Override
public InvoiceItem addInvoiceItem(InvoiceItem invoiceItem) {
return invoiceItemRepository.save(invoiceItem);
}
}
But when I call this api endpoint to create an invoiceItem for an invoice, the items property in the invoiceOptional has the 'Unable to evaluate the expression Method threw 'org.hibernate.exception.GenericJDBCException' exception, while other properties are fine.
You can try to exclude the circular tostring\hashcode reference on one POJO:
#ToString(exclude = "invoice")
#EqualsAndHashCode(exclude = "invoice")
#Entity
#Getter #Setter
public class InvoiceItem {
or to do it by using #EqualsAndHashCode.Exclude & #ToString.Exclude on the field
#Entity
#Data
public class InvoiceItem {
#ManyToOne
#EqualsAndHashCode.Exclude #ToString.Exclude
#JoinColumn(name="invoice_id", nullable = false)
private Invoice invoice;

Spring Boot Jpa mapping

I am learning spring boot data JPA.
Here is my code
Users.java
#Entity
#Table(name = "user_Details")
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="user_id", referencedColumnName="user_id")
private Set<usersAction> usersAction;
usersAction.java
#Entity
#Table(name="user_Action")
public class usersAction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="action_id")
private Integer Action_id;
#Column(name="user_id")
private Integer id;
#Column(name="users_Role")
private String usersRole;
UsersRepository.java
package com.demo.repository;
import java.util.Set;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import com.demo.model.Users;
#Repository
public interface UsersRepository extends JpaRepository<Users, Integer> {
#Query(value="select u.password,u.user_id,u.name,ua.users_Role from user_details as u"
+ " inner join user_action as ua"
+ " on u.user_id=ua.user_id",nativeQuery=true)
Set<Users> findById();
}
UsersController.java
#RestController
#RequestMapping("/users")
public class usersController {
#Autowired
UsersRepository usersRepository;
#GetMapping("/all")
public List<Users> getAll() {
return usersRepository.findAll();
}
#RequestMapping("/byid")
public Set<Users> findByName()
{
Set<Users> obj1=usersRepository.findById();
return obj1;
}
}
When I am accessing this http://localhost:8080/users/byid
I am getting output as id ,name ,password from users_details table and also Action_id,id and users_role from users_Action
But I am expecting in Result only name from users_detail and usersRole from users_Action table
What i need to change for that?
You need to create interfaces like this
interface UsersSummary {
String getName();
String getName();
UsersActionSummary getUsersAction();
interface UsersActionSummary {
String getUsersRole();
Integer getId();
Integer getAction_Id();
}
}
And then change your repository method to return this
#Query(value="select u.password,u.user_id,u.name,ua.users_Role from user_details as u"
+ " inner join user_action as ua"
+ " on u.user_id=ua.user_id",nativeQuery=true)
Collection<UsersSummary> findById();

Categories