#GetMapping doesn't display CreditCards under username. #PostMapping doesn't create a new card for user, it only updates it - spring

My User Class looks as follows:
#Entity
#Table(name = "Users")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userID")
private Integer userID;
#Column(name = "username",nullable = false, unique = true)
private String username;
#Column(name = "password")
private String password;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Column(name = "email")
private String email;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<CreditCard> creditCard;
//Constructor, Getters and Setters
CreditCard Class looks :
#Entity
#Table(name = "CreditCards")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class CreditCard {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "cardID", nullable = false)
private Integer cardID;
#Column(name = "cardName")
private String cardName;
#Column(name = "cardNumber")
private BigInteger cardNumber;
#Column(name = "expirationDate")
private Integer expirationDate;
#Column(name = "securityCode")
private Integer securityCode;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "user_id", nullable = false)
#JsonIgnore
private User user;
//Constructor, Getters and Setters
CreditCard Resource:
#RestController
#RequestMapping("/geektext/users")
class CreditCardResource {
#Autowired
CreditCardRepository cardsRepository;
#Autowired
UserRepository userRepository;
//Displays CreditCard By Username Search
#GetMapping("/{username}/cards")
public Optional<CreditCard> getCardsByUsername(#PathVariable String username) throws NotFoundException {
if (!userRepository.findByUsername(username).isPresent()){
throw new NotFoundException("User '" + username + "' not found");
}
return cardsRepository.findById(userRepository.findByUsername(username).get().getUserID());
}
//Creates New Card for User
#PostMapping("/{userID}/cards")
public CreditCard loadCard(#PathVariable String userID, #RequestBody CreditCard creditCard) throws NotFoundException {
return userRepository.findByUsername(userID).map(user -> {creditCard.setUser(user);
return cardsRepository.save(creditCard);
}).orElseThrow(() -> new NotFoundException("User '" + userID + "' not found"));
}
}
There is also a UserResource.java , UserRepository (Interface) and CreditCardRepository) but these do not affect the problem I am having. Please how can I fix getting list of cards for User passing username on url. How can user create New/ More than one CreditCard instead of updating the one he has.

You are trying to get a credit-card using your userID
return cardsRepository.findById(userRepository.findByUsername(username).get().getUserID());
Instead, you could search for your credit-card by user. To do this, you should create a method in the credit-card repository interface.
List<CreditCard> findByUser(User user);
Then call this method from your controller
return cardsRepository.findByUser(userRepository.findByUsername(username).get())
The post method has a similar problem. You are trying to get user by username, but passing the userID. Also you set user to your new credit-card, but you don't add a new credit-card to your user. (And change the name of credit-cards variable in the User class to creditCards)
return userRepository.findByUsername(userID).map(user -> {creditCard.setUser(user);
return cardsRepository.save(creditCard);
}).orElseThrow(() -> new NotFoundException("User '" + userID + "' not found"));
This will be much better. Test it yourself and change something if I wrote something wrong
User user = userRepository.findById(userID);
user.getCreditCards().add(creditCard);
creditCard.setUser(user);
userRepository.save(user);
NotFoundException I guess you can handle by yourself.

Update: I had to create an ID for each credit card since if the same ID is assigned on the creation of each new credit card, then program would treat it like if I was the same one that was trying to be updated.

Related

Confused why getting a User from Repository fixed "failed to lazily initialize a collection of role" compared to using SecurityContextHolder

My goal was to pass a List of Businesses to the model from the controller to display it in a view and I have succeeded, but have a bit of confusion.
When I initially tried using:
public User getCurrentAuthenticatedUser() {
UserDetailsImpl user = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return user.getUser();
}
#GetMapping("")
public String list(Model model) {
model.addAttribute("businesses", userService.getCurrentAuthenticatedUser().getBusinesses());
return "business/list";
}
I got this error: "failed to lazily initialize a collection of role: com.xyz.User.businesses could not initialize proxy - no Session"
Then I tried:
#GetMapping("")
public String list(Model model) {
int userId = userService.getCurrentAuthenticatedUser().getId();
User user = userService.getById(userId); // gets User using Spring Data JPA UserRepository
List<Business> businesses = user.getBusinesses();
model.addAttribute("businesses", businesses);
return "business/list";
}
And this worked perfectly fine.
What was the issue using the first method. It seemed more simple rather than calling a User from the UserRepository. I've seen some posts that say you should use EAGER fetching, but that's just seems like a bandaid solution.
From the beginner's understanding: Since fetch type is LAZY the businesses don't exist yet in the User but are fetched on demand later on so there shouldn't be an issue.
Edit: After more thought I remembered that with basic Hibernate you would have to create Transactions and commit transactions. I'm assuming that User is not within a Transaction that's why I can't get businesses using the 1st method.
What would be a better solution to fetch the current Authenticated user? And that user's attributes such as a list of businesses.
Model Classes:
Business:
#Entity
#Table(name = "businesses")
public class Business {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private LocalDate date;
#ManyToOne(cascade={CascadeType.MERGE})
#JoinColumn(name="user_id")
private User user;
public Business() {
}
public Business(String name, String description, LocalDate date, User user) {
...
}
public Business(Long id, String name, String description, LocalDate date, User user) {
...
}
... getters/setters
}
USER:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private boolean enabled;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable( name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
#OneToMany(fetch = FetchType.LAZY, mappedBy="user", cascade={CascadeType.MERGE})
private List<Business> businesses;
... getters/setters
}

Spring boot UserDetailsService Multi-User with extra fields

I have a spring boot project that has 3 types of users (Admin, Expert, Customer) and the application is for Experts that register on site for giving services like fixing computers to Customers that are asking help in site.
I have an inheritance of different kind of User types as following.
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "USER_TYPE", discriminatorType = DiscriminatorType.INTEGER)
public abstract class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private Set<String> roles = new HashSet<>();
// getter & setter...
}
#Entity
#DiscriminatorValue("1")
public class Admin extends User {
}
#Entity
#DiscriminatorValue("2")
public class Expert extends User {
private Byte[] expertPhoto;
private String password;
// some other fields & getter & setter...
}
#Entity
#DiscriminatorValue("3")
public class Customer extends User {
private Long credit;
private Set<CustomerOrder> orders = new HashSet<>();
// some other fields & getter & setter...
}
I want to use spring boot security and implement UserDetailsService, my problem is that how to design when I have different User types (Expert, Customer, etc.)?
I want users to be able to have different roles (admin, expert, customer) with one username.
How should I design my system to solve these requirements?
Your role modal seems a bit off. It is better to have a single type of User and fill it with list of a new Role entity. The new User entity will look like the following:
#Table(name = "user")
#Entity
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "username", unique = true, nullable = false)
private String username;
#Column(name = "password", nullable = false)
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "user_role",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")},
)
private Set<Role> roles;
// getters and setters & other fields user can have
}
And the Role entity will look like this:
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "role_name", unique = true, nullable = false)
private String roleName;
#ManyToMany(mappedBy = "roles")
private Set<User> users;
}
Then, you need to implement org.springframework.security.core.userdetails.User interface to use as a concrete implementation of spring security class Useron your UserDetailsService. Notice that this class is also called User and is different than the User class on your system.
public class MyUserDetail extends User {
private String otherFieldsLikePhoto; // you can add different fields like this to keep extra information
public MyUserDetail(String username, String password, Collection<? extends GrantedAuthority> authorities, String otherFieldsLikePhoto) {
super(username, password, authorities);
this.otherFieldsLikePhoto = otherFieldsLikePhoto;
}
}
Then, you can create your UserDetailsService by implementing org.springframework.security.core.userdetails.UserDetailsService of spring security.
What you will achieve UserDetailsService is to load the user in the MyUserDetail format we just created. It will be something like this:
public class MyUserDetailsService implements UserDetailsService {
private final UserReadService userReadService; // put your service to get user from db
public MyUserDetailsService(UserReadService UserReadService) {
this.userReadService = UserReadService;
}
#Override
public UserDetails loadUserByUsername(String username) {
User user = userReadService.getByUsername(username); // get user from db
String otherFieldsLikePhoto = getUserPhotoOrAnythingElse(user); // get your extra fields however you want
return new MyUserDetail(
user.getUsername(),
user.getPassword(),
getAuthoritySetOfUser(user), // notice how we embed roles to UserDetail
otherFieldsLikePhoto
);
}
// this function is not necessary but useful to calculate authority set calculation on helper
private Set<SimpleGrantedAuthority> getAuthoritySetOfUser(User user) {
Set<Role> userRoles = user.getRoles(); // get roles of user like ADMIN, EXPERT etc.
Set<SimpleGrantedAuthority> authorities = roles.stream()
.map(rolex -> new SimpleGrantedAuthority(rolex.getRoleName()))
.collect(Collectors.toSet());
return authorities;
}
}

how to add object with fk to table in jparepository

i had scheme of user parking and detail parking.
user can park many times (one to many)
im trying to add detail parking object to my db, but i dont have idea how to add the fk from the user in the row of the table, its gave me null there.
(ignore from the logic of the model, i just want to understood the logic how can i the object with fk of ther entity)
this is my code:
#PostMapping("/parking")
public String saveCarParking(#ModelAttribute("user") parkingUsers parkingUsers) {
// parkingUsers[id, firstName, lastName, license]
parkingUsers p = new parkingUsers("jhon", "nash", "248651355");
parkingUsersService.saveParkingUser(p);
// parkingDetails[id, entryDate, entryTime, exitDate, exitTime, user_id(FK)]
parkingDetails d = new parkingDetails(LocalDate.now(), null, LocalDate.now(), null);
parkingDetailsService.saveParkingUser(d);
//how i connect parkingDetails object with fk of parkingUsers?
//it adding now row of parkingDetails but without the fk of user
return "redirect:/parkingList";
}
parking user entity:
#Entity
#Table(name ="users")
public class parkingUsers {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "license")
private String license;
#OneToMany(mappedBy = "parkingUsers", cascade = CascadeType.ALL, orphanRemoval = true)
private List<parkingDetails> parkingDetails = new ArrayList<parkingDetails>();
public parkingUsers() {
}
public parkingUsers(String firstName, String lastName, String license) {
this.firstName = firstName;
this.lastName = lastName;
this.license = license;
}
//setter gettrs and tostring...
entity class of details parking
#Entity
#Table(name ="details")
public class parkingDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "entry_date")
private LocalDate entryDate;
#Column(name = "entry_time")
private LocalDateTime entryTime;
#Column(name = "exit_date")
private LocalDate exitDate;
#Column(name = "exit_time")
private LocalDateTime exitTime;
#ManyToOne
#JoinColumn(name="user_id")
private parkingUsers parkingUsers;
public parkingDetails() {}
public parkingDetails(LocalDate entryDate, LocalDateTime entryTime, LocalDate exitDate, LocalDateTime exitTime) {
this.entryDate = entryDate;
this.entryTime = entryTime;
this.exitDate = exitDate;
this.exitTime = exitTime;
}
//test
// public parkingDetails(LocalDate entryDate, LocalDateTime entryTime, LocalDate exitDate, LocalDateTime exitTime, int user_id ) {
// this.entryDate = entryDate;
// this.entryTime = entryTime;
// this.exitDate = exitDate;
// this.exitTime = exitTime;
// this.parkingUsers.setId(user_id);
// }
//setter gettrs and tostring...
In the ParkingDetails entity, you can have a setter for "parkingUsers" variable to set user object.
In your REST api's saveCarParking() method, before calling "parkingDetailsService.saveParkingUser(d);" you can pass the user object to ParkingDetails using setter created in ParkingDetails.
This should work. No need to explicitly extract the user_id from user's object to pass into ParkingDetails.
Adding one more parameter of type ‘parkingUsers‘ in the constructor of ‘ ParkingDetails’ to initialize user in parking class will also work.
(Apart, it is a good practice to start the class name with a capital letter e.g. instead of having class name as parkingDetails, it should be ParkingDetails.)

How can I retrieve all the children of a record in this Hibernate #ManyToOne relation?

I am working on a Spring Boot project using Spring Data JPA and Hibernate mapping. I have the following doubt about how can I implement the following query.
I have an User entity class like this:
#Entity
#Table(name = "portal_user")
#Getter
#Setter
public class User implements Serializable {
private static final long serialVersionUID = 5062673109048808267L;
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(name = "first_name")
#NotNull(message = "{NotNull.User.firstName.Validation}")
private String firstName;
#Column(name = "middle_name")
private String middleName;
#Column(name = "surname")
#NotNull(message = "{NotNull.User.surname.Validation}")
private String surname;
#Column(name = "sex")
#NotNull(message = "{NotNull.User.sex.Validation}")
private char sex;
#Column(name = "birthdate")
#NotNull(message = "{NotNull.User.birthdate.Validation}")
private Date birthdate;
#Column(name = "tax_code")
#NotNull(message = "{NotNull.User.taxCode.Validation}")
private String taxCode;
#Column(name = "e_mail")
#NotNull(message = "{NotNull.User.email.Validation}")
private String email;
#Column(name = "pswd")
#NotNull(message = "{NotNull.User.pswd.Validation}")
private String pswd;
#Column(name = "contact_number")
#NotNull(message = "{NotNull.User.contactNumber.Validation}")
private String contactNumber;
#Temporal(TemporalType.DATE)
#Column(name = "created_at")
private Date createdAt;
#Column(name = "is_active")
private boolean is_active;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
#JsonManagedReference
private Set<Address> addressesList = new HashSet<>();
#ManyToMany(cascade = { CascadeType.MERGE })
#JoinTable(
name = "portal_user_user_type",
joinColumns = { #JoinColumn(name = "portal_user_id_fk") },
inverseJoinColumns = { #JoinColumn(name = "user_type_id_fk") }
)
Set<UserType> userTypes;
#ManyToOne(fetch = FetchType.EAGER)
#JsonProperty("subagent")
private User parent;
public User() {
super();
}
public User(String firstName, String middleName, String surname, char sex, Date birthdate, String taxCode,
String email, String pswd, String contactNumber, Date createdAt, boolean is_active) {
super();
this.firstName = firstName;
this.middleName = middleName;
this.surname = surname;
this.sex = sex;
this.birthdate = birthdate;
this.taxCode = taxCode;
this.email = email;
this.pswd = pswd;
this.contactNumber = contactNumber;
this.createdAt = createdAt;
this.is_active = is_active;
}
}
The instances of this class represents users of my system. An user can have a single specific parent (the concept is similar to that of a referral: an user can bring another user in the system). This is handled by this ManyToOne recursive relationship:
#ManyToOne(fetch = FetchType.EAGER)
#JsonProperty("subagent")
private User parent;
Basically an user contains is parent (who bring him\her into the platform). It works fine. So retrieving an user I can easily retrieve the information of who is its parent (it is contained into the retrieved User object).
Now I need to implement the inverse behavior: I have to define a "query" that starting from a parent retrieve all its children.
The previous User entity class maps the following DB table:
The highlighter parent_id contains the FK that define this recursive relationship. So it contains the PK of another user that is the parent.
I have this UserRepository repository interface (it extents the JpaRepository interface)
public interface UsersRepository extends JpaRepository<User, Integer> {
User findByemail(String email);
List<User> findByUserTypes_TypeName(String typeName);
}
As you can see I am using a "query by method" style. Is it possiblem implement a behavior like this using "query by method" style? (in case also JPQL could be fine)
You can do this
List<User> findByParent_Id(Integer id);
Or you can do this
#Query("SELECT u FROM User u WHERE u.id = ?1")
List<User> getReferredUsers(Integer id);
The relationship between the user and the parent is unidirectional in the given code. By making it bidirectional, it is easy to query the data in either ways.
Refer to below code to make it bidirectional. Also ensure the relevant FetchType to avoid the performance risk. Here FetchType.LAZY is used for one to many association so it queries the data using the proxy reference when needed.
#ManyToOne(fetch = FetchType.EAGER)
#JsonProperty("subagent")
#JsonBackReference
private User parent;
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parent")
private Set<User> userSet = new HashSet<>();
Child entities are fetched only when parent.getUserSet is used because of the FetchType.Lazy
public Set<User> getUsers(int id) {
User parent = userRepository.getById(id);
return parent.getUserSet();
}

Hibernate #OneToMany relation cascade option not working

I design simple 1:N schema , Account(1):AccountProfileImage(N).
Below codes are entity codes.
// Account.java
#Entity
#Table(name = "account")
#Getter
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#EntityListeners(AuditingEntityListener.class)
public class Account {
#GeneratedValue
#Id
#Column(name = "id")
private Long id;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "password", nullable = false)
private String password;
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name", nullable = false)
private String lastName;
#CreatedDate
#Column(name = "created_at")
private LocalDateTime createdAt;
#OneToMany(mappedBy ="account",cascade = CascadeType.ALL)
private final List<AccountProfileImage> profileImages= new ArrayList<>();
#Builder
public Account(String email,String firstName,String lastName,String password){
this.email=email;
this.firstName=firstName;
this.lastName=lastName;
this.password=password;
}
}
// AccountProfileImage.java
#Entity
#Table(name = "account_profile_image")
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#Getter
#EntityListeners(AuditingEntityListener.class)
public class AccountProfileImage {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "account_id")
private Account account;
#Column(name = "image_url")
private String imageURL;
#CreatedDate
#Column(name = "created_at")
private LocalDateTime createdAt;
#Builder
public AccountProfileImage (Account account,String imageURL){
this.account=account;
// this.account.addProfileImage(this);
this.imageURL=imageURL;
}
}
and this is test code for AccountProfileRepository code.
#Test
#Rollback(value = false)
public void saveAccountProfileImageTest() throws Exception {
// given
Account account = Account.builder()
.email("user#email.com")
.firstName("user")
.lastName("user")
.password("1234")
.build();
AccountProfileImage profileImage = AccountProfileImage.builder()
.account(account)
.imageURL("pathToURI")
.build();
AccountProfileImage profileImage2 = AccountProfileImage.builder()
.account(account)
.imageURL("pathToURI2")
.build();
accountRepository.save(account);
// when
List<AccountProfileImage> images = profileImageRepository.findAllByAccount_IdOrderByCreatedAtDesc(1L);
// then
// this assertion fail
assertThat(images.size()).isEqualTo(2);
}
What i want to expect find by List of images whose size is 2 because I add CscadeType.ALL in Account entity class and when creating AccountProfileImage object, I set account member variable in AccountProfileImage object.
this.account=account;
Did I something wrong?
I add below method in Account entity and 2 lines at test code, then it works fine. Do i have to do this everytime? Is there exist another better approach or best practice?
// Account Entity
public void addProfileImages(AccountProfileImage image){
this.profileImages.add(image);
}
// test code
account.addProfileImages(profileImage);
account.addProfileImages(profileImage2);
accountRepository.save(account);
// when
List<AccountProfileImage> images = profileImageRepository.findAllByAccount_IdOrderByCreatedAtDesc(1L);
// then
// this assertion pass
assertThat(images.size()).isEqualTo(2);
In Bi-directional relationships, you have to define the association on both ends of the relationship. To avoid any issues, you can update the helper addProfileImage(..) method to add the AccountProfileImage to the list and set account property of the image to the current account. This is the best practice as this way, the helper method will set up the association across both ends of the bi-directional relationship.
E.g.
public void addProfileImages(AccountProfileImage image){
this.profileImages.add(image); // Add image to profileImages
image.setAccount(this); // Set account property to the current account
}
Test:
#Test
#Rollback(value = false)
public void saveAccountProfileImageTest() throws Exception {
// given
Account account = Account.builder()
.email("user#email.com")
.firstName("user")
.lastName("user")
.password("1234")
.build();
AccountProfileImage profileImage = AccountProfileImage.builder()
.imageURL("pathToURI")
.build();
AccountProfileImage profileImage2 = AccountProfileImage.builder()
.imageURL("pathToURI2")
.build();
// Setup association
account.addProfileImage(profileImage);
account.addProfileImage(profileImage2);
accountRepository.save(account);
// when
List<AccountProfileImage> images = profileImageRepository.findAllByAccount_IdOrderByCreatedAtDesc(1L);
// then
// this assertion fail
assertThat(images.size()).isEqualTo(2);
}

Resources