i am currently adding Auditing to my Entities. That is working fine but i want to save the Id of the User which is related to the Entity inside the #CreatedBy and #LastModifiedBy Columns and not the Name which i have right now.
I am also using Spring Security + JWT to get the User which is working fine.
How can i achieve this with the following Configuration?
User Entity:
#Entity(name = "User")
#Table(name = "user")
public class User extends Auditable<String> {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "UserSequence")
#GenericGenerator(
name = "UserSequence",
strategy = "com.simagdo.crmAPI.utils.SequenceIdGenerator",
parameters = {
#org.hibernate.annotations.Parameter(name = SequenceIdGenerator.INCREMENT_PARAM, value = "1"),
#org.hibernate.annotations.Parameter(name = SequenceIdGenerator.VALUE_PREFIX_PARAMETER, value = "130"),
#org.hibernate.annotations.Parameter(name = SequenceIdGenerator.NUMBER_FORMAT_PARAMETER, value = "%05d")
}
)
#Column(name = "Id")
private String id;
#Column(
name = "UserName",
length = 80,
unique = true)
private String userName;
#Column(name = "Password")
private String password;
}
AuditorAware:
public class AuditAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
return Optional.of("Simagdo");
}
}
The config:
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaConfig {
#Bean
public AuditorAware<String> auditorAware() {
return new AuditAwareImpl();
}
}
My Auditable Class:
#MappedSuperclass
public abstract class Auditable<U> {
#CreatedBy
#OneToOne(
orphanRemoval = true,
cascade = {
CascadeType.PERSIST,
CascadeType.REMOVE
}
)
private User createdBy;
#CreatedDate
#Column(
name = "CreatedDate",
nullable = false,
updatable = false
)
private LocalDateTime createdDate;
#LastModifiedBy
#OneToOne(
orphanRemoval = true,
cascade = {
CascadeType.PERSIST,
CascadeType.REMOVE
}
)
private User lastModifiedBy;
#LastModifiedDate
#Column(
name = "LastModifiedDate",
nullable = false
)
private LocalDateTime lastModifiedDate;
}
try adding following on top of entity class that contains #CreatedBy & #LastModifiedBy:
#EntityListeners({AuditingEntityListener.class})
Related
Im learning, and so far i created many to many bidirectional database - user can create many groups and group can have many users - and i cannot find a way for my GroupsController Post mapping to work, from my understanding, it requires to get firstly Users id, in order to set the right relationship in Join table for Group, because the relationship should be set only when user create/join group, not when user create sign up procedure. Postman throws 500 and intelliJ:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because the return value of "com.ilze.highlight.entity.Groups.getId()" is null] with root cause
java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because the return value of "com.ilze.highlight.entity.Groups.getId()" is null
I use lombok - #Data, #Getter, therefore getId() should be available for use from Group class. My GroupsController with POST mapping when user decides to create a new group:
#RestController
#RequestMapping("api/groups") // pre-path
public class GroupsController{
#Autowired
private GroupsService groupsService;
#Autowired
private UserService userService;
#Autowired
private final GroupsRepository groupsRepository;
#Autowired
private UserRepository userRepository;
public GroupsController(GroupsRepository groupsRepository) {
this.groupsRepository = groupsRepository;
}
#GetMapping("/all-groups")
public List<Groups> getGroups(){
return (List<Groups>) groupsRepository.findAll();
}
#PostMapping("/user/{usersId}/create-group")
public ResponseEntity<Groups> createGroup(#PathVariable(value = "usersId") Long usersId, #RequestBody Groups groupRequest){
Groups group = userRepository.findById(usersId).map(users -> {
long groupsId = groupRequest.getId();
// add and create new group
users.addGroup(groupRequest);
return groupsRepository.save(groupRequest);
}).orElseThrow(() -> new ResourceNotFoundException("Not found user with id = " + usersId));
return new ResponseEntity<>(group, HttpStatus.CREATED);
}
}
Group database class:
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Table(name = "group_collection")
public class Groups {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name ="group_name", unique = true, nullable = false, length = 20)
private String groupName;
#Column(name = "size", nullable = false)
private int size;
#Column(name = "strict", nullable = false)
private boolean strict;
#Column(name = "open", nullable = false)
private boolean open;
#Column(name ="description", length = 300)
private String description;
#Column(name = "create_time", nullable = false)
private LocalDateTime createTime;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
},
mappedBy = "groups")
#JsonIgnore
private Set<User> users = new HashSet<>();
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
And Users class for database:
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "username", unique = true, nullable = false, length = 100)
private String username;
#Column(name = "password", nullable = false)
private String password;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "create_time", nullable = false)
private LocalDateTime createTime;
#Enumerated(EnumType.STRING)
#Column(name = "role", nullable = false)
private Role role;
#Transient
private String accessToken;
#Transient
private String refreshToken;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
})
#JoinTable(name = "groups_x_user",
joinColumns = { #JoinColumn(name = "users_id") },
inverseJoinColumns = {#JoinColumn(name = "groups_id")})
private Set<Groups> groups = new HashSet<>();
public void addGroup(Groups group) {
this.groups.add(group);
group.getUsers().add(this);
}
public void removeGroup(long id){
Groups group = this.groups.stream().filter(g ->
g.getId() == id).findFirst().orElse(null);
if(group != null){
this.groups.remove(group);
group.getUsers().remove(this);
}
}
For reference my GroupsService implementation:
#Service
public class GroupsServiceImpl implements GroupsService{
private final GroupsRepository groupsRepository;
public GroupsServiceImpl(GroupsRepository groupsRepository) {
this.groupsRepository = groupsRepository;
}
#Override
public Groups saveGroup(Groups group) {
group.setCreateTime(LocalDateTime.now());
return groupsRepository.save(group);
}
#Override
public Optional<Groups> findByGroupName(String groupName) {
return groupsRepository.findByGroupName(groupName);
}
}
You need to persist the object from request. And since you have Many-2-Many relation, you can insert related object from both sides. In your case: just add existing user to the newly created group
The method will look something like that:
#PostMapping("/user/{usersId}/groups")
public ResponseEntity<Groups> createGroup(#PathVariable(value = "usersId") Long usersId, #RequestBody Groups groupRequest) {
Groups createdGroup = userRepository.findById(usersId)
.map(user -> {
groupRequest.setId(null); // ID for new entry will be generated by entity framework, prevent override from outside
groupRequest.getUsers().add(user); // add relation
return groupsRepository.save(groupRequest);
}).orElseThrow(() -> new ResourceNotFoundException("Not found user with id = " + usersId));
return new ResponseEntity<>(createdGroup, HttpStatus.CREATED);
}
I have a spring service class where I'm loading a JPA object (target) via CRUD. This target class has a one-to-may mapping that is set to lazy loading.
I would like to query this object inside a spring service method that is annotated with #Transactional and avoid that the childs are being loaded.
When I execute the following code all child data is loaded and laziness is ignored.
#Override
#Transactional
public boolean changeState(boolean enabled, final EventType eventType, final String deviceSerialNumber) {
final UniqueUser user = userService.getUser();
final Target target = targetRepository.findEventsByUserIdAndTenantIdAndTargetDeviceId(user.getUserId(), user.getTenantId(), deviceSerialNumber);
//here everything gets loaded
if (target == null) {
return false;
}
final List<EventHistory> events = target.getEvents().stream()
.filter(event -> event.getEventType() == eventType)
.filter(event -> event.isActive() != enabled)
.collect(Collectors.toList());
events.forEach(event -> event.setActive(enabled));
if (events.isEmpty()) {
return false;
}
return true;
}
Mappings:
#ToString
#Entity
#Table(name = "target")
public class Target {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", unique = true)
private UUID id;
#Column(name = "user_id")
private String userId;
#Column(name = "tenant_id")
private String tenantId;
#Column(name = "target_device_id")
private String targetDeviceId;
#Column(name = "target_type")
private TargetType targetType;
#OneToMany(mappedBy = "target", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JsonManagedReference
private List<EventHistory> events = new ArrayList<>();
public void addEvents(EventHistory event) {
events.add(event);
event.setTarget(this);
}
}
#Entity
#Data
#Table(name = "event_history")
public class EventHistory {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", unique = true)
private UUID id;
#Column(name = "active")
private boolean active;
#OneToMany(mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
private List<EventTimestamp> timestamps = new ArrayList<>();
#ManyToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "target_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonBackReference
private Target target;
public void addTimestamps(EventTimestamp eventTimestamp) {
timestamps.add(eventTimestamp);
eventTimestamp.setEvent(this);
}
}
#Entity
#Data
#Table(name = "event_timestamp")
public class EventTimestamp {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", unique = true)
private UUID id;
#Column(name = "due_timestamp")
private Timestamp dueTimestamp;
#Column(name = "period")
private String period;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "event_id", nullable = false)
#JsonBackReference
private EventHistory event;
So my question is how to keep lazy loading inside transaction annotated functions?
My first assumption that the root cause had been the wrongly implemented repository function was wrong. The real issue was the #ToString annotation. This added the one-to-many event collection to toString(). While being inside the transaction and accessing the object, toString got invoked and the the collection was loaded.
Solution was to exclude the collection from toString via.
#OneToMany(mappedBy = "target", cascade = CascadeType.ALL, orphanRemoval = true)
#ToString.Exclude
private List<EventHistory> events;
I figured it out. The problem was ins the Repository code. The findBy method is expecting a List instead of a single object.
My original repository looked like this
#Repository
public interface TargetRepository extends CrudRepository<Target, UUID> {
Target findEventsByUserIdAndTenantIdAndTargetDeviceId(String userId, String tenantId, String targetId);
}
Changing it to the below version fixed it.
#Repository
public interface TargetRepository extends CrudRepository<Target, UUID> {
List<Target> findEventsByUserIdAndTenantIdAndTargetDeviceId(String userId, String tenantId, String targetId);
}
The problem is that by adding a "message" object type to the "StoreActivity" entity, the first time, it does so without any problem, when iterating a second time, the program throws the exception:Multiple representations of the same entity
I have tried in the parent entity with all types of "cascades" and it does not work
Entity StoreActivity :
#Entity
#Table(name="store_activities")
public class StoreActivity implements Serializable {
#EmbeddedId
StoreActivityPk storeActivityPk = new StoreActivityPk();
#ManyToOne
#JoinColumn(name = "store_id", insertable = false, updatable = false)
#MapsId("storeId")
private Store store;
#ManyToOne
#JoinColumn(name = "activity_id", insertable = false, updatable = false)
#MapsId("activityId")
private Activity activity;
#NotEmpty
#Column(length = 500, nullable=true)
private String comment;
#OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL,CascadeType.DETACH,CascadeType.PERSIST,CascadeType.REFRESH,CascadeType.REMOVE})
#JoinColumns({
#JoinColumn(name="date_activity", referencedColumnName="dateActivity"),
#JoinColumn(name="store_id", referencedColumnName="store_id"),
#JoinColumn(name="activity_id", referencedColumnName="activity_id"),
#JoinColumn(name="store_activity_hour", referencedColumnName="storeActivityHour")
})
private List<Message> messages;
//Getters and setters ...
}
StoreActivityPk :
#Embeddable
public class StoreActivityPk implements Serializable{
private Long activityId;
private Long storeId;
#Temporal(TemporalType.DATE)
private Date dateActivity;
#Temporal(TemporalType.TIME)
private Date storeActivityHour;
}
Message entity :
#Entity
#Table(name = "messages")
public class Message implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long messageId;
#NotEmpty
#Column(length = 1000, nullable=false)
private String messageContent;
#Column(length = 1, nullable=false)
private Boolean messageRead;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="user_id")
private UserCheck userCheckSender;
#Temporal(TemporalType.TIMESTAMP)
private Date dateMessage;
//Getters and setters
}
Controller :
public class StoreActivityController {
#PostMapping("/save")
public String save(#Valid Message message, BindingResult result, Model model, RedirectAttributes flash,
SessionStatus sessionStatus, Authentication authentication,
#RequestParam(name = "storeId", required = true) Long storeId,
#RequestParam(name = "activityId", required = true) Long activityId,
#RequestParam(name = "dateActivity", required = true) #DateTimeFormat(pattern="yyyy-MM-dd") Date dateActivity) {
if (result.hasErrors()) {
model.addAttribute("title", "Mensajes");
model.addAttribute("subtitle", "Mensajes de la actividad diaria");
return "frontend/message/activitymessage";
}
UserCheck userCheck = (UserCheck) authentication.getPrincipal();
message.setUserCheckSender(userCheck);
StoreActivity storeActivity = storeActivityService.findById(activityId, storeId, new Date());
storeActivity.addMessage(message);
storeActivityService.saveStoreActivity(storeActivity);
sessionStatus.isComplete();
flash.addFlashAttribute("success", "Mensaje Enviado");
return "redirect:/message/activity/" + storeActivity.getStoreActivityPk().getActivityId() + "/store/"
+ storeActivity.getStoreActivityPk().getStoreId();
}
}
Actual result console :
2019-08-08 17:44:54.918 WARN 5024 --- [io-8090-exec-10] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.dao.InvalidDataAccessApiUsageException: Multiple representations of the same entity [cl.tricotcorp.app.checklist.models.entity.UserCheck#1] are being merged. Detached: [cl.tricotcorp.app.checklist.models.entity.UserCheck#8f06f5d]; Managed: [cl.tricotcorp.app.checklist.models.entity.UserCheck#46781997]; nested exception is java.lang.IllegalStateException: Multiple representations of the same entity [cl.tricotcorp.app.checklist.models.entity.UserCheck#1] are being merged. Detached: [cl.tricotcorp.app.checklist.models.entity.UserCheck#8f06f5d]; Managed: [cl.tricotcorp.app.checklist.models.entity.UserCheck#46781997]]
I have the following bean:
public class TerminalAdmin {
#Id
#Column(name = "admin_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long adminId;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "phone")
#Size(max = 255)
private String phone;
#Size(max = 255)
#Column(name = "name")
private String name;
#Column(name = "registration_date")
#Temporal(TemporalType.TIMESTAMP)
private Calendar createDate;
#Column(name = "password", nullable = false)
#Size(min=1, max = 255, message = "введите пароль длиной от 1 до 255 символов")
private String password;
#ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name = "admin_role", joinColumns = {
#JoinColumn(name = "admin_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false) })
private Set<AdminRole> adminRoles;
#Column(name = "blocked")
private boolean blocked;
...
}
and this:
public class AdminRole {
#Id
#Column(name = "role_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long id;
#Column(name = "role")
private String role;
....
}
Inside controller:
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#Valid TerminalAdmin terminalAdmin,
BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
from client side I send following request:
terminalAdmin comes to the method looks like this
Why spring writes values into role field?
How to force spring write 250/251 into id field?
P.S.
I tried to write
InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(AdminRole.class, new PropertyEditorSupport() {
public void setAsText(String name) {
....
}
});
}
but setAsText method doesn't invoke.
This is not a good practice to populate model objects into to forms since Spring can bind fields to object even if they are not populated into the view if your init binder is not properly configured.
Easiest way is to create DTO objects, eg. you could create AdminTerminalDTO or AdminTerminalForm wich you populate to the view.
The Form could contain same fields as AdminTerminal excluding ID field or any other sensitive fields. You cant insert new ID's from the view since it can cause DB integrity errors.
After successful validation you just persist your model object filling it with DTO/Form Object.
Moreover your JSR-303 Annotations seem to be not used in a proper way.
The #Size Annotation is not proper a validation to check String length. You have to use #Length instead. You use #Size to check length of an arrays. #Size also works on Strings but #Length is more accurate.
You can't just send an Integer and just try to bind to your Set(spring does some weird binding as you can see now) . Instead you already done addNewAdmin method in your controller wich already informs that it adds an Admin User.
You have to assign admin role on the server side right in this method. First you can use DTO wich will contain eg. username,password and other fields. You annote them with proper JSR-303 Annotations. Using bindingResult you check if there were any validation errors. If form is validated fine, you just convert your DTO/Form object to Model object. Then you can add admin role and persist your model object.
I can write some example code if this tips are not enough.
EDIT:
public class TerminalAdminDTO {
private String username;
#Length(max = 255)
public String getUsername(){
return username;
}
public void setUsername(String username){
this.username = username;
}
public TerminalAdmin convertToTerminalAdmin(){
TerminalAdmin terminalAdmin = new TerminalAdmin();
terminalAdmin.setUsername(this.username);
return terminAdmin;
}
}
#Entity
#Table
public class TerminalAdmin {
#Id
#Column(name = "admin_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long adminId;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "phone")
#Size(max = 255)
private String phone;
#Size(max = 255)
#Column(name = "name")
private String name;
#Column(name = "registration_date")
#Temporal(TemporalType.TIMESTAMP)
private Calendar createDate;
#Column(name = "password", nullable = false)
#Size(min=1, max = 255, message = "введите пароль длиной от 1 до 255 символов")
private String password;
#ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name = "admin_role", joinColumns = {
#JoinColumn(name = "admin_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false) })
private Set<AdminRole> adminRoles;
#Column(name = "blocked")
private boolean blocked;
...
}
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#Valid TerminalAdminDTO terminalAdminDTO,
BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
if(result.hasErrors()){
return "errorPage";
}else{
userService.createAdminUser(terminalAdminDTO);
return "successPage";
}
}
#Service
#Transactional
public class UserServiceImpl implements UserService {
private final int ADMIN_ROLE_ID = 0;
#Autowired
EntityManager entityManager;
public void createAdminUser(TerminalAdminDTO terminalAdminDTO){
TerminalAdmin terminalAdmin = terminalAdminDTO.convertToTerminalAdmin();
AdminRole adminRole = entityManager.find(AdminRole.class,ADMIN_ROLE_ID);
terminalAdmin.getAdminRoles().add(adminRole);
entityManager.create(terminalAdmin);
}
}
I wrote it as an example of way doing it, this is not a ready-made code
I'm designing a db model where I want to have:
User(id, role_id) (UserDetails)
Permission(role_id, permission) (GrantedAuthority)
Role(id, description)
I'm using Hibernate and Spring security. I want every role to have list of permissions (GrantedAuthorities) using role_id rather than specific user having that. I'm kinda lost designing that. This is what I've came up with so far:
public class User implements UserDetails, Serializable {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "role_id", insertable = true, updatable = false)
private int roleId;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "user", cascade = CascadeType.ALL)
private List<Permission> permissions;
}
public class Permission implements GrantedAuthority, Serializable {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "role_id", insertable = false, updatable = false)
private int roleId;
#Column(name = "permission")
private String permission;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "role_id", nullable = false, insertable = false, updatable = false)
private User user;
}
I've omitted the not so important code. The Role class isn't important for Spring security.
I know I'm making a huge mistake somewhere but I just can't seem to figure out how to fix it. The issue is that it joins those two objects using user.id instead of user.roleId. I've tried different annotations but I just can't hook it up correctly.
So the raw issue probably is that I'm trying to join 2 objects using a property from one and PK from another which might be a mistake.
Edit: I've also tried to specify referencedColumnName on Permission class but it didn't work either. User can't log in.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "role_id", nullable = false, insertable = false, updatable = false, referencedColumnName = "role_id")
private User user;
Why would you want to have a design that can backfire you in the future? You really have to think ahead and apply good design practices on your project. This topic pops up here almost every day.
In Spring context, a role actually is an authority. I have no idea why this is made so complex by design by them. You either can have a 1) very simple approach where you assign a role which in fact is authority to an user or 2) more complex solution which includes user, role and permission. The idea would be to assign permissions to roles and assign roles to user. In this solution role entity only serves a purpose of grouping the granted permissions together as one bundle, but through your authentication manager you assign permissions through roles.
Note: I am using common base #MappedSuperclass for my entities.
First, have an User entity:
#Entity
#Table(name = "user_t")
public class User extends BaseEntity {
#Column(name = "username", nullable = false, unique = true)
private String userName;
#Column(name = "password", nullable = false)
private String password;
#ManyToMany
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> role = new HashSet<Role>();
// builder/getters/setters
}
Role entity
#Entity(name = "role_t")
#Column(name = "role_name", nullable = false)
private String roleName;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "role_permission", joinColumns = #JoinColumn(name = "role_id"), inverseJoinColumns = #JoinColumn(name = "permission_id"))
private Set<Permission> permissions;
// getters/setters
}
Permission entity
#Entity(name = "permission_t")
public class Permission extends BaseEntity implements GrantedAuthority {
#Column (name = "permission_name", nullable = false)
private String permissionName;
public String getPermissionName() {
return permissionName;
}
public void setPermissionName(String permissionName) {
this.permissionName = permissionName;
}
#Override
public String getAuthority() {
return permissionName;
}
#Override
public int hashCode() {
return permissionName.hashCode();
}
#Override
public boolean equals(Object obj) {
if(obj == null) return false;
if(!(obj instanceof Permission)) return false;
return ((Permission) obj).getAuthority().equals(permissionName);
}
Now in your AuthenticationManager or whatever you decide to use, you loop throug the roles and assign the permissions that are assigned to the roles to the user, if that makes sense.
CustomAuthenticationProvider
public class AppAuthProvider implements AuthenticationProvider {
private static final String PERMISSION_PREFIX = "ROLE_PERMISSION_";
// get the logging user info
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Collection<GrantedAuthority> permissions = new HashSet<GrantedAuthority>();
for (Role role : user.getRole()) {
for (Permission perm : role.getPermissions()) {
GrantedAuthority permission = new SimpleGrantedAuthority(PERMISSION_PREFIX + perm.getPermissionName());
permissions.add(permission);
}
}
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user, null, permissions); // user object you get from service/repository
return authToken;
}
}
I think you have to re-consider your schema, see the image below. work from here. You need a many-many mapping table for mapping permissions to role.
public class User implements UserDetails, Serializable {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#JoinColumn(name = "role_id", mappedBy= 'user", insertable = true, updatable = false)
private Role role;
}
public class Permission implements Serializable {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "permission")
private String permission;
#ManyToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL)
#JoinTable(name = "role_permissions", catalog = "schema", joinColumns = {
#JoinColumn(name = "id", nullable = false, updatable = false) }, inversJoinColumns =
{ #JoinColumn(name = "id", nullable = false, updatable = false)}
private Set<Role> roles;
}
public class Role implements Serializable {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "permission")
private String permission;
#ManyToMany(fetch=FetchType.LAZY, mappedBy = "permission")
public Set<Permissions> permisions;
}
Update: see the new schema diagram.