Using the Binder class to bind ManyToMany via Checkbox

What is the best solution to bind collections using the Binder class via Checkbox for example: Set userRoles = new HashSet<>();
I can do method to set roles without Binder like this:

void assignRole(){
        Boolean valueCheckboxAdmin = roleAdmin.getValue();
        Boolean valueCheckboxBaker = roleBaker.getValue();

        Set<RolesEntity> listOfRoles = userEntity.userRoles();

        if(valueCheckboxAdmin==true){
            addRole(userRoleRepository.findFirstByRoleName("Admin"));

        }
        if(valueCheckboxAdmin==false){
            for (RoleEntity x: listOfRoles
                 ) {if(x.getRoleName().equals("Admin")){
                     deleteRole(x);} }
        }
        if(valueCheckboxBaker==true){
            addRole(userRoleRepository.findFirstByRoleName("Baker"));
        }
        if(valueCheckboxBaker==false){
            for (RoleEntity k: listOfRoles
                 ) {if(k.getRoleName().equals("Baker")){
                     deleteRole(k); } }
        }
        }

But after saving it is impossible to get value from database in Checkbox.
I want to use Binder class for this.

@Entity
@Table(name = "user")
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "idUser")
    private Long idUser;
    private String name;
    private String surname;
    

	@ManyToMany (fetch = FetchType.EAGER)
    Set<RolesEntity> userRoles = new HashSet<>();

    public UserEntity(){

    }
	
	//getters and setters

	
@Entity
public class RoleEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "idRole")
    private Long idRole;
    private String roleName;
	
	public RoleEntity(){

    }
}

@SpringComponent
@UIScope
public class UserEditor extends VerticalLayout implements KeyNotifier {

private final UserRepository userRepository;
private final RoleRepository roleRepository;

private UserEntity userEntity;

TextField name = new TextField("Name");
TextField surname = new TextField("Surname");
Checkbox roleAdmin = new Checkbox("Admin");
Checkbox roleBaker = new Checkbox("Baker");


Button save = new Button("Save");
Button cancel = new Button("Cancel");
Button delete = new Button("Delete", VaadinIcon.TRASH.create());

HorizontalLayout userFields1 = new HorizontalLayout(name,surname);
HorizontalLayout roles = new HorizontalLayout(roleAdmin, roleBaker);
HorizontalLayout actions = new HorizontalLayout(save,cancel,delete);

Binder<UserEntity> binder = new Binder<>(UserEntity.class);

public UserEditor(UserRepository userRepository, RoleRepository roleRepository){
        this.userRepository=userRepository;
        this.roleRepository=roleRepository;
        
		add(userFields1,roles,actions);
		
		
		binder.bind(name, "name");
        binder.bind(surname, "surname");
		binder.forField(roleAdmin).bind(???????????);
		binder.forField(roleBaker).bind(???????????);
		
		
		//others
		
		}

Karol,

Have you looked at CheckboxGroup? It works with Set<> so you should be able to bind it directly.

Thanks Martin for the information, but it looks like that CheckBoxGroup is not available in Vaadin 14, which I use.

I’m on 14.1.5 and I have it.

You’re right. CheckboxGroup is available for Vaadin 10+, although it is not visible as an official component for Vaadin 14 on the site at the moment.
How do you got him set up Martin?

CheckboxGroup<RoleEntity> userRoles = new CheckboxGroup<>();

userRoles.setItems(Stream.of(admin,baker).collect(Collectors.toSet()));

binder.bind(userRoles,"userRoles");

???
In this configuratiuon it is still impossible to get value from database in CheckboxGroup after saving.

Maybe a binder converter should be used in this case …?

If you create your binding manually with binder.forField(userRoles).bind(some-read-method, some-write-method) your IDE will give you a compile time error if your types are not matching.

Thanks for the answer. I used a manual binding in Java 7 style, but I still have have a problem with visibility of the CheckBox selection in UserEditor. Other fields are filled with data when I click on object. What is wrong?

binder.bind(userRoles , new ValueProvider<UserEntity, Set<RoleEntity>>() {
            @Override
            public Set<RoleEntity> apply(UserEntity userEntity) {
                Set<RoleEntity> listOfRoles = userEntity.getUserRoles();
				return listOfRoles;
            }},
                    new Setter<UserEntity, Set<RoleEntity> >(){
                @Override
                        public void accept(UserEntity userEntity, Set<RoleEntity> roles){
                    userEntity.setUserRoles (roles);
                }
            });

Not sure I understand. Can you elaborate?

[this is how it looks LINK]
(https://guidestudio.pl/show.png)

Karol,

Have you implemented the equals() method in your RoleEntity? If not, and you are using JPA/Spring Data/Hibernate etc., you should be aware that when you save and load an entity it will become new object in memory.

The checkboxgroup - as well as other components that uses a collection of objects - depends on the equals() method to determine if the objects are the same.

Consider this scenario:

// Create role
RoleEntity admin = new RoleEntity("Administrator");
admin = roleService.save(admin);

.. 

// create user that uses admin role
UserEntity john = new User("John", admin);
john = userService.save(john);

..

// Load john
john = userService.find(...);

if (john.hasRole(admin)) {
  // do something
}

The above code will only work if the RoleEntity overrides the equals() method. If RoleEntity does not implement an equals, the CheckboxGroup will see the Items (the options) as being different from the values, even if they are both “admin” roles and for that reason the checkboxes will appear as unchecked when you load.

There are different ways you can construct equals and hashcode. At the most basic level, you should check that object and class matches and that Id is the same.

Example:

   UUID roleId;

   @Override
   public int hashCode() {
      final int prime = 31;
      int result = super.hashCode();
      result = prime * result + ((roleId == null) ? 0 : roleId.hashCode());
      return result;
   }

   @Override
   public boolean equals(Object obj) {
      if (this == obj)
         return true;
      if (!super.equals(obj))
         return false;
      if (getClass() != obj.getClass())
         return false;
      Role other = (Role) obj;
      if (roleId != null) return roleId.equals(other.roleId);
	  return false;
   }

The addition of hashCode () and equals () implementations in the RoleEntity class solved my problem. Thank you so much for the professional explanation. Should I override these methods in every entity class?

Karol,

AFAIK all flow components that deals with collections of data relies directly or indirectly on equals. This includes select, combobox, checkboxgroup, grid etc. So all entities that are used in these components will need equals to work.

Also, there are more sophisticated ways of checking equals and hashcode. Do a google search on java equals, specifically as it relates to entities. Some advocate checking all member fields for equality. For entitites, you may consider the objects as being equal if the ID/primary key is the same, regardless of other member fields.