I'm currently taking a look into Hibernate (with Spring, if important) and I tried to make a small web application in which someone can register and log in. There is a secured area which is protected by Spring Security and I already got my UserDetailsService working, so Spring Security is using the Hibernate stuff to do the login.
Now I am working on the registration for new users. I thought about how to do it and I came to having a separate table for the activations which would basically have 3 columns: activationId, username and activation_code. The activation code would be some kind of hash but I guess its not relevant for this question.
My question basically is, how to do the relationship between my users table and the activations. I need to link the username from the activations to the username from the users table.
Do I have to use a One To One Mapping? Or how can I link exactly one column from another table into the activation table?
Here's my User Entity:
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "username", unique = true, nullable = false, length = 45)
private String username;
#Column(name = "password", nullable = false, length = 64)
private String password;
#Column(name = "enabled", nullable = false)
private boolean enabled;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<UserRole> userRole = new HashSet<UserRole>(0);
#Column(name = "name")
private String name;
#Column(name = "lastname")
private String lastname;
public User() {
}
public User(String username, String password, boolean enabled) {
this.username = username;
this.password = password;
this.enabled = enabled;
}
public User(String username, String password, boolean enabled, Set<UserRole> userRole) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.userRole = userRole;
}
// Getters and setters for all attributes omitted
}
If you need the UserRole Entity too, just tell me and I will add it but I think it's not needed. Here is also the current version of my UserActivation Entity, but of course it is not finished yet:
import javax.persistence.*;
#Entity
#Table(name = "activations")
public class UserActivation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long activationId;
#Column(length = 64)
private String activationCode;
#Column
private String userName; // here we need some magic relationship
}
You can use the ManyToOne mapping :
#Entity
#Table(name = "activations")
public class UserActivation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long activationId;
#Column(length = 64)
private String activationCode;
#ManyToOne(optional=false)
#JoinColumn(name="USER_NAME")
private User user; // since your User Id is username, the join column will be username
}
if you want the username to be unique, wich means one activation for a single user :
#Entity
#Table(name = "activations",uniqueConstraints=#UniqueConstraint(columnsnames="USER_NAME"))
Related
I am currently making project which should have Many-To-Many relationship between groups and users. I should be able to update an user and add one. I have no troubles with adding an user, it gets saved properly. However, when I try to update user, it gives an error that definitely has something to do with that hibernate tries to save groups information. Here is my code.
User Entity
#Entity
#Table(name = "user")
public class User {
#Id
#Column(name = "email")
private String email;
#Column(name = "firstname")
private String name;
#Column(name = "lastname")
private String surname;
#Column(name = "age")
private int age;
#Column(name = "gender")
private String gender;
#ManyToMany
#JoinTable(
name = "user_group"
, joinColumns = #JoinColumn(name = "email")
, inverseJoinColumns = #JoinColumn(name = "group_id")
)
Group entity
#Entity
#Table(name = "category")
public class Group {
#Id
#Column(name = "group_name")
private String name;
#Column(name = "description")
private String description;
#ManyToMany(mappedBy = "groupList")
private List<User> userList = new ArrayList<User>();
Controller save method
#PostMapping("/addUser")
public String addUser(#ModelAttribute("user") User user) {
service.saveUser(user);
return "redirect:/allUsers";
}
Service save method
#Override
#Transactional
public void saveUser(User user) {
userDAO.saveUser(user);
}
DAO save method
#Override
public void saveUser(User user) {
entityManager.merge(user);
}
java.sql.SQLSyntaxErrorException: Unknown column 'group1_.description' in 'field list
The problem was that I altered my column names in DB but forgot to update it in Entity class. Got to be careful next time:)
i have Three entities User, Institution and Role.
1)one to many between user and institution
2)and many to many between User and Role
-------user-------
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="user_Id")
private int userId;
#Column(name="name")
private String name;
#Column(name="lastname")
private String lastname;
#Column(name="email")
private String email;
#Column(name="password")
private String password;
#Column(name="isActive")
private boolean isActive;
#Column(name="lastActive")
private String lastActive;
#Column(name="createdDate")
private String createdDate;
#Column(name="isBlocked")
private boolean isBlocked;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "institution_id", nullable = false)
#JsonIgnoreProperties(value = {"user"})
private Institution institution;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_has_role",
joinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "user_id",
nullable = false, updatable = true)},
inverseJoinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "role_id",
nullable = false, updatable = true)})
#JsonIgnoreProperties(value = {"users"})
private Set<Role> roles = new HashSet<>();
}
-------institution-------
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
#Table(name = "institution")
public class Institution {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="institution_Id")
private int institutionId;
#Column(name="name")
private String name;
#Column(name="type")
private String type;
#Column(name="location")
private String location;
#OneToMany(mappedBy = "institution", fetch = FetchType.LAZY)
#JsonIgnoreProperties(value = {"institution" , "user"})
private Set<User> user;
}
-------role-------
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="role_Id")
private int roleId;
#Column(name="name")
private String name;
#Column(name="description")
private String description;
#ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
#JsonIgnoreProperties(value = {"roles"})
private Set<User> users = new HashSet<>();
}
Those are my 3 entities and tables in MySql
i have 7 roles
• Super-User
• Bank-Admin
• Bank-Support
• Bank-Service
• Merchant-Admin
• Merchant-Support
• Merchant-service
The super-User can create a user of any role
#PostMapping("/addUser")
public String addUser(#RequestBody User user) {
String rawpassword = user.getPassword();
String encodedpasswrod = passwordencoder.encode(rawpassword);
user.setPassword(encodedpasswrod);
userrepository.save(user);
return "user saved with name: " + user.getName();
}
this api works and i can set the role to anything in my api json body
But want that if the User is Bank-Admin he can only create Bank-Support and Bank-Service
im trying to create a new API which can only create a user with those 2 specific roles.
and then restrict the bank admin to access the other API that can create users of any kind.
is there any other way to do it and if no how can i do that...
You have to implement your custom implementation of User Entitlement.
Like according to login person, you will get that login person role, and according to your criteria just put validation like check that entity he is trying to add is he eligible to create it.
Map<String, List<String>> roleUserAccessMap = new HashMap<>();
roleUserAccessMap.put("Bank-Admin", Arrays.asList("Bank-Support", "Bank-Service"));
Just check like below
String loginPersonRole="Bank-Admin"; //This value should get from logged-in person context
if(roleUserAccessMap.containsKey(loginPersonRole) && roleUserAccessMap.get(loginPersonRole).contains(newuserrole) ){
//proceed ahead with Add api
}else{
System.out.println("You do not have enough privileage to create Use");
}
This will help you.
I trying to make relation between phonebook and user through jpa, when the current logged in user creates a contact the foreign key of user in table phonebook remains null. I checked couple of question here but it did'not work for me.
Phonebook
#Entity
#Table(name = "Phonebook")
public class Phonebook {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "phonebook_id")
private Long id;
#Column(name = "phone", length = 15, nullable = false)
private String phoneNumber;
#Column(name = "firstname", length = 50, nullable = false)
private String firstName;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
//getters and setters
User
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#Column(name = "email")
private String email;
#Column(name = "password")
#Length(min = 5, message = "*Your password must have at least 5 characters")
#org.springframework.data.annotation.Transient
private String password;
#OneToMany(mappedBy = "user")
private List<Phonebook> phonebooks;
//getters and setters
PhonebookController
#RequestMapping(value = {"/home/phonebook"}, method = RequestMethod.GET)
public String showPage(Model model, #RequestParam(defaultValue = "0") int page){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findUserByEmail(auth.getName());
model.addAttribute("data",phonebookRepository.findAllByUserId(user.getId(),PageRequest.of(page,10)));
model.addAttribute("currentPage",page);
return "/home/phonebook";
}
#PostMapping("/home/phonebook/save")
public String save (Phonebook p){
phonebookRepository.save(p);
return "redirect:/home/phonebook";
}
PhonebookRepository
#Repository("phonebookRepository")
public interface PhonebookRepository extends JpaRepository<Phonebook,Integer> {
List<Phonebook> findAllByUserId(Long id, Pageable pageable);
}
what You have to do the first create a user object and set the id and then persist the phone book.
You must persist PhoneBook together with your User.
User u = new User();
// Set properties for your User...
PhoneBook p = new PhoneBook();
// Set properties for your phonebook...
// Store phone book to user:
u.setPhoneBook(Collections.singletonList(p));
userRepository.save(p);
I dont know why my List of embeddable objects persists to database despite transaction rollback.
I have my Entity User with List of Embeddable Role.
When I'm persisting user with the same username as already existing in database, i see an exception : "RollbackException: Unable to commit: transaction marked for rollback", which is perfectly fine as i have the unique username column. But I dont know why, despite the rollback, list of roles of that user persists to database.
It's like transaction is working only for entity class and persists list of embedded role to database every time even when it shouldnt ( becouse of rollback).
What am I doing wrong ?
Thanks for any hints
(I'm using OpenJPA 2.3.0)
User Code:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(unique = true, nullable = false)
private String username;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(
name = "ROLES",
joinColumns = #JoinColumn(name = "OWNER_ID")
)
private List<Role> roles;
private String password;
private boolean enabled;
public User(String username, String password) {
this.username = username;
this.password = password;
this.enabled = true;
roles = new ArrayList<Role>();
Role userrole = new Role();
userrole.setRole("ROLE_USER");
userrole.setUsername(username);
roles.add(userrole);
}
//getters and setters
}
Role code:
#Embeddable
public class Role {
#Column(name="username")
String username;
#Column(name="userrole")
String role;
//getters and setters
}
I am trying to implement DAOs to work with Spring Security database authentication in Hibernate/JPA2. Spring uses following relations and associations in order to represent user & roles:
repesented as postgresql create query:
CREATE TABLE users
(
username character varying(50) NOT NULL,
"password" character varying(50) NOT NULL,
enabled boolean NOT NULL,
CONSTRAINT users_pkey PRIMARY KEY (username)
);
CREATE TABLE authorities
(
username character varying(50) NOT NULL,
authority character varying(50) NOT NULL,
CONSTRAINT fk_authorities_users FOREIGN KEY (username)
REFERENCES users (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
Using the on-board implementations of GrantedAuthorities, UserDetailsService and UserDetailsmanager, everything is fine. However, I am not satisfied with the JDBC implementation of Spring and would like to write my own ones. In order to do so, I tried to create a representation of the relations by following business objects:
The user entity:
#Entity
#Table(name = "users", uniqueConstraints = {#UniqueConstraint(columnNames = {"username"})})
public class AppUser implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = -8275492272371421013L;
#Id
#Column(name = "username", nullable = false, unique = true)
private String username;
#Column(name = "password", nullable = false)
#NotNull
private String password;
#OneToMany(
fetch = FetchType.EAGER, cascade = CascadeType.ALL,
mappedBy = "appUser"
)
private Set<AppAuthority> appAuthorities;
#Column(name = "accountNonExpired")
private Boolean accountNonExpired;
#Column(name = "accountNonLocked")
private Boolean accountNonLocked;
#Column(name = "credentialsNonExpired")
private Boolean credentialsNonExpired;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "personalinformation_fk", nullable = true)
#JsonIgnore
private PersonalInformation personalInformation;
#Column(name = "enabled", nullable = false)
#NotNull
private Boolean enabled;
public AppUser(
String username,
String password,
boolean enabled,
boolean accountNonExpired,
boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<? extends AppAuthority> authorities,
PersonalInformation personalInformation
) {
if (((username == null) || "".equals(username)) || (password == null)) {
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.appAuthorities = Collections.unmodifiableSet(sortAuthorities(authorities));
this.personalInformation = personalInformation;
}
public AppUser() {
}
#JsonIgnore
public PersonalInformation getPersonalInformation() {
return personalInformation;
}
#JsonIgnore
public void setPersonalInformation(PersonalInformation personalInformation) {
this.personalInformation = personalInformation;
}
// Getters, setters 'n other stuff
And the authority entity as an implementation of GrantedAuthorities:
#Entity
#Table(name = "authorities", uniqueConstraints = {#UniqueConstraint(columnNames = {"id"})})
public class AppAuthority implements GrantedAuthority, Serializable {
//~ Instance fields ================================================================================================
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "username", nullable = false)
private String username;
#Column(name = "authority", nullable = false)
private String authority;
// Here comes the buggy attribute. It is supposed to repesent the
// association username<->username, but I just don't know how to
// implement it
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "appuser_fk")
private AppUser appUser;
//~ Constructors ===================================================================================================
public AppAuthority(String username, String authority) {
Assert.hasText(authority,
"A granted authority textual representation is required");
this.username = username;
this.authority = authority;
}
public AppAuthority() {
}
// Getters 'n setters 'n other stuff
My problem is the #ManyToOne assoc. of AppAuthorities: It is supposed to be "username", but trying and doing so throws an error, because I've got to typify that attribute as String ... while Hibernate expects the associated entity. So what I tryied is actually providing the correct entity and creating the association by #JoinColumn(name = "appuser_fk"). This is, of course, rubbish, because in order to load the User, I will have the foreign key in username, while Hibernate searches for it in appuser_fk, which will always be empty.
So here is my question: any suggestion on how to modify the above metioned code in order to get a correct JPA2 implementation of the data model?
Thanks
You AppAuthority doesn't need username at all. Spring Security can't depend on it because it depends on the GrantedAuthority interface which doesn't have any methods to access username.
But the better practice is to decouple your domain model from Spring Security. When you have a custom UserDetailsService, you don't need to mimic neither Spring Security's default database schema nor its object model. Your UserDetailsService can load your own AppUser and AppAuthority and then create UserDetails and GrantedAuthoritys based on them. This leads to cleaner design with better separation of concerns.
This looks like the classic Hibernate problem of using a domain-specific key. A possible fix would be to create a new primary key field; e.g. userId int for the Users and Authorities entities / tables, remove Authorities.userName, and change Users.userName to a unique secondary key.
There is one more way that decouples the UserDetailsService from JPA/Hibernate.
You can model your User and Authority class as you like and use this while defining userDetailsService in configuration:-
<sec:jdbc-user-service data-source-ref="webDS"
id="userDetailsService"
users-by-username-query="SELECT USER_NAME,PASSWORD,TRUE FROM CUSTOMER WHERE USER_NAME=?"
authorities-by-username-query="SELECT c.USER_NAME,r.ROLE_NAME from CUSTOMER c
JOIN CUSTOMER_ROLE cr ON c.CUSTOMER_ID = cr.CUSTOMER_ID
JOIN ROLE r ON cr.ROLE_ID = r.ROLE_ID
WHERE USER_NAME=?" />
This way you can define fine tuned SQL query to fetch user and roles from your database.
All you need to take care of is the table and column name.