【问题标题】:Is it possible to signal hibernate to load a @ManytoMany association with a set of proxys (not a lazy set)?是否可以向休眠发出信号以加载与一组代理(而不是惰性集)的 @ManytoMany 关联?
【发布时间】:2020-08-02 19:28:26
【问题描述】:

问题:

我有UserRole 实体,并且关系是多对多的。首先我执行roleRepository.findById() 来获取角色,然后执行role.getUsers().forEach(user -> System.out.println(user.getId())); 来打印关联用户的ID。

当调用第一个方法时,会向role 表发出查询。并且当调用第二种方法时,会向role_usersuser 表发出连接查询。

是否可以通过任何注释让 hibernate 知道它应该使用一组 Proxy user 对象创建 Role 对象,以便在上述两种方法中永远不会使用 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()));
      }
    }

注意

我确实知道如何通过单独的查询获取关联实体的 ID。这是一个关于问题中突出显示的休眠注释的可能性的问题。

【问题讨论】:

  • 我认为不可能随心所欲。您可以编写一个 JPQL 查询来检索这些 ID。
  • 我也认为这是不可能的,如果您只需要 id 最好只为 id 列表编写 JPQL 查询。
  • 我也有同样的想法,但我可以看到它适用于 ManyToOne 和上面的情况,role.getUsers().size()@LazyCollection(EXTRA) 一起使用,这让我大吃一惊。而且这在逻辑上是可能的,因为所有相关数据都在rolerole_users 表中,所以问题是hibernate 实现是否支持它
  • 好的,我不知道ManyToOne 有效! ManyToOne 是如何工作的?我试过了,但它不适合我。
  • 看这个。如果Id的访问类型是property,你可以做getId()stackoverflow.com/questions/35013661/…

标签: spring-boot hibernate jpa spring-data-jpa


【解决方案1】:

从代理的集合对象中获取集合大小确实为您提供了正确的基数,因为它基本上是集合的大小。但只要您需要这些值,就会触发查询。

对此没有特殊的注释。但是可以自己做。您必须编写自己的惰性初始化程序。 (看看 Hibernate Core 源代码中的org.hibernate.proxy 包)会很乱,可能不值得。

最好的办法是使用 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);
}

【讨论】:

  • 感谢您抽出宝贵时间,但我知道如何通过单独的查询获取 ID。我的问题是关于任何注释的具体说明。因为对于ManyToOne,hibernate 允许创建一个代理,您可以在其中获取 id 而无需初始化代理
【解决方案2】:

理论上这可以通过加载图来完成,但我怀疑这在没有字节码增强的情况下是否可行,即使这样,我也不确定这种额外的惰性是否适用于所有情况。

话虽如此,这是Blaze-Persistence Entity Views 的完美用例。

Blaze-Persistence 是基于 JPA 的查询构建器,它支持 JPA 模型之上的许多高级 DBMS 功能。我在它之上创建了实体视图,以允许在 JPA 模型和自定义接口定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您以您喜欢的方式定义目标结构,并通过 JPQL 表达式将属性(getter)映射到实体模型。由于属性名称用作默认映射,因此您通常不需要显式映射,因为 80% 的用例是拥有作为实体模型子集的 DTO。

模型的 DTO 映射可能如下所示简单

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

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

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

但是 Spring Data 集成允许您几乎像 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

它只会获取您告诉它获取的映射

【讨论】:

    猜你喜欢
    • 2020-12-12
    • 2013-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-15
    • 2014-05-29
    • 1970-01-01
    相关资源
    最近更新 更多