Часто замечаю за собой, что иногда даже какие то элементарные технические моменты, не всегда удается четко выразить. Для этого и решил попробовать пописать статьи на небольшие темы. Тема данной статьи в заголовке.

Я занимаюсь разработкой веб приложений. В текущем проекте есть пользователи, каждый имеет свою роль или несколько совмещенных. В двух словах о том, как организовано хранение ролей. Для этого используются битовые маски.

У пользователя есть поле rolesbit типа int, в котором хранится его роль. Каждой роли соответствует какая то маска. У админа это 1, у оператора 1 со сдвигом на одну позицию влево (0b1 << 1), у менеджера 1 со сдвигом на две позиции (0b1 << 2) и тд.

Таким образом, если у пользователя rolesbit == 0b111, то он имеет роль админа (0b001), роль оператора (0b010) и роль менеджера (0b100).

Для проверки наличия роли у пользователя, нужно использовать маску соответствующей роли и операцию побитового логического «И».

Например, пусть у одного пользователя rolesbit = 0b111, тогда 111 & 010 = 010, у пользователя есть роль оператора.

У другого пользователя rolesbit = 0b100, тогда 100 & 010 = 000, у пользователя нет роли оператора. То есть маска показывает в каком разряде должна стоять единичка, результат умножения роли пользователя на маску показывает какое значение содержится в этом разряде, если 1 то пользователь имеет эту роль, если 0, то нет.

Появилась задача дополнить фильтр по пользователям фильтрацией по ролям. Фильтрация происходила путем извлечения объектов из базы удовлетворяющим параметрам фильтра. И тут возникла проблема, мы использовали Hibernate, но он не поддерживает операцию побитового «и» (нет в синтаксисе hql символа "&"). Конечно, можно написать sql запрос:

session.createSQLQuery("select * from user where rolesbits & 2 !=0").list()

Без проблем получим всех «операторов».

Но, сейчас нужно было добавить один параметр поиска в уже существующий запрос, и очень не хотелось переписывать уже имеющуюся часть кода. Оказывается, отсутствие "&" в hql не так уж смертельно.

Нужно лишь вспоминить Перевод из 2 в 10 систему счисления:

Решается благодаря следующим правилам:
1. Деление на 2^х сместит биты на х позиций влево (напр 111/10 = 11)
2. Число по модулю 2 дает 1 если крайний бит установлен (в примере выше установлен)

Таким образом, общее истинное выражение (rolesbit / bitsMask) % 2 = 1 будет показывать что у пользователя с ролью rolesbit есть роль с маской bitsMask.

Соответственно, запрос будет выглядеть таким образом:

session.createQuery("select u from user u where MOD(u.rolesbits/:bitsMask , 2) = 1").setParameter("bitsMask", role).list();