Можно ли сигнализировать о гибернации для загрузки ассоциации @ManytoMany с набором прокси (не для ленивого набора)? - PullRequest
3 голосов
/ 02 августа 2020

Проблема:

У меня есть сущности User и Role, и связь много ко многим. Сначала я выполняю roleRepository.findById(), чтобы получить роль, а затем role.getUsers().forEach(user -> System.out.println(user.getId()));, чтобы распечатать идентификаторы связанных пользователей.

При вызове первого метода запрос выполняется к таблице role. И когда вызывается второй метод, запрос на соединение выдается к таблицам role_users и user.

Можно ли сообщить спящему режиму через любую аннотацию, что он должен создать Role объект с набором Proxy user объектов, так что во время двух вышеуказанных методов таблица User никогда не используется?

Например, я могу аннотировать ассоциацию коллекции с помощью @LazyCollection(EXTRA), а затем role.getUsers().size() отлично работает без использования таблицы user.

Код

    @Entity
    public class Role {

      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      @Access(AccessType.PROPERTY)
      private Long id;

      private String name;

      @ManyToMany
      private Set<User> users;
      
      ... getters and setters
    }
    @Entity
    public class User {

      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      @Access(AccessType.PROPERTY)
      private Long id;

      private String name;

      @ManyToMany(mappedBy = "users")
      private Set<Role> roles;
      
      ..getters and setters
    }
    public interface UserRepository 
        extends JpaRepository<User, Long> {
    }
    public interface RoleRepository 
        extends JpaRepository<Role, Long> {
    
      Role findByName(String roleName);
    }
    @Service
    public class UserService {

      @Autowired
      private UserRepository userRepository;

      @Autowired
      private RoleRepository roleRepository;

      @Transactional
      public void setUpSomeUsersAndRoles(){
        User userOne = userRepository.save(new User("user-1"));
        User userTwo = userRepository.save(new User("user-2"));
        User userThree = userRepository.save(new User("user-3"));

        Role roleOne = roleRepository.save(new Role("ADMIN"));

        userOne.setRoles(singleton(roleOne));
        userTwo.setRoles(singleton(roleOne));
        userThree.setRoles(singleton(roleOne));

        Set<User> users 
                = new HashSet<>(asList(userOne, userTwo, userThree));
        roleOne.setUsers(users);
      }

      @Transactional
      public void findRoleByName(){
        Role role = roleRepository.findByName("ADMIN");

        //I want the following to be executed
        //without query issued to user table
        role.getUsers()
           .forEach(user -> System.out.println(user.getId()));
      }
    }

Примечание

Я знаю, как получить идентификаторы связанных сущностей с помощью отдельного запроса. Это специально вопрос о возможности аннотации спящего режима, как указано в вопросе.

Ответы [ 2 ]

1 голос
/ 03 августа 2020

Получение размера коллекции из проксируемого объекта коллекции действительно дает вам правильную мощность, потому что это в основном размер коллекции. Но как только вам понадобятся значения, запросы будут запущены.

Для этого нет специальной аннотации. Но сделать это можно и самому. Вам нужно будет написать свой собственный Lazy Initializer. (Взгляните на пакет org.hibernate.proxy в исходном коде Hibernate Core) Он будет беспорядочным и, вероятно, того не стоит.

Лучше всего использовать JPQL или Native, примерно так:

public interface RoleRepository extends JpaRepository<Role, Long> {

    Role findByName(String roleName);

    @Query(value="SELECT users_id FROM role_users WHERE roles_id = ?1", nativeQuery = true)
    List<Long> findUserIdsForRole(Long roleId);
}
0 голосов
/ 03 августа 2020

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

Сказав это, это идеальный вариант использования для представлений сущностей Blaze-Persistence .

Blaze-Persistence - это построитель запросов поверх JPA, который поддерживает многие расширенные функции СУБД на основе модели JPA. . Я создал Entity Views поверх него, чтобы упростить сопоставление между моделями JPA и моделями, определяемыми пользовательским интерфейсом, что-то вроде Spring Data Projection на стероидах. Идея состоит в том, что вы определяете свою целевую структуру так, как вам нравится, и сопоставляете атрибуты (геттеры) через выражения JPQL с моделью сущности. Поскольку имя атрибута используется в качестве сопоставления по умолчанию, вам обычно не нужны явные сопоставления, поскольку в 80% случаев использования должны быть DTO, которые являются подмножеством модели сущности.

Сопоставление DTO для вашей модели может выглядеть так просто, как следующее

@EntityView(User.class)
interface UserDto {
    Integer getId();
}
@EntityView(Role.class)
interface RoleDto {
    Integer getId();
    String getName();
    Set<UserDto> getUsers();
}

Запрос - это вопрос применения представления сущности к запросу, самый простой из которых - это просто запрос по идентификатору.

RoleDto dto = entityViewManager.find(entityManager, RoleDto.class, id);

Но интеграция Spring Data позволяет вам использовать его почти как Spring Data Projection: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring -data-features

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

...