【问题标题】:Hibernate JPA loop休眠 JPA 循环
【发布时间】:2021-01-20 04:48:13
【问题描述】:

我创建了一个实体类:

@Entity
@Table(name="users")
@Getter @Setter
public class UserModel implements Serializable {

    @Setter(AccessLevel.NONE)
    @Getter(AccessLevel.NONE)
    private static final long serialVersionUID = -5608230793232883579L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(nullable = false, unique = true)
    private String userId;

    @Column(nullable = false, length = 50)
    private String firstName;

    @Column(nullable = false, length = 50)
    private String lastName;

    @Email
    @Column(nullable = false, length = 120, unique = true)
    private String email;

    @Column(nullable = false)
    private String encryptedPassword;

    private Boolean emailVerificationStatus = false;

    private String emailVerificationToken;

    @ManyToMany(cascade= { CascadeType.PERSIST }, fetch = FetchType.EAGER )
    @JoinTable(
            name = "user_role",
            joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
            inverseJoinColumns=@JoinColumn(name = "role_id", referencedColumnName = "id"))
    private List<RoleModel> roles;

    @JsonManagedReference
    @OneToMany(mappedBy = "user")
    private List<ProjectModel> projects;
}

对于项目列表,我还有一个实体类:

@Entity
@Table(name= "projects")
@Getter @Setter
public class ProjectModel implements Serializable {
    @Setter(AccessLevel.NONE)
    @Getter(AccessLevel.NONE)
    public static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(nullable = false, unique = true)
    private String projectId;
    
    // ... 

    @Column
    @JsonManagedReference
    @OneToMany(mappedBy = "project")
    private List<ObjectiveModel> objectives;

    // ...

    @JsonBackReference
    @ManyToOne(
            cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH },
            fetch = FetchType.LAZY
    )
    private UserModel user;

}

我还使用 DTO 层与数据库进行通信:

@Getter @Setter
public class UserDto implements Serializable {

    @Setter(AccessLevel.NONE)
    @Getter(AccessLevel.NONE)
    private static final long serialVersionUID = -5352357837541477260L;

    // contains more information than models used for rest
    private long id;
    private String userId;
    private String firstName;
    private String lastName;
    private String email;
    private String password;
    private String encryptedPassword;
    private String emailVerificationToken;
    private Boolean emailVerificationStatus = false;

    private List<String> roles;
    private List<ProjectDto> projects;
}

每个实体都有自己的 Dto 等价物。我可以创建一个用户。我的问题是尝试登录。我的 userServiceImpl 实现了 Spring Security UserService。这是我的实现:

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    UserModel userModel = userRepository.findByEmail(email);
    if(userModel == null)
        throw new UsernameNotFoundException("User with email "  + email + " not found");

    return new UserPrincipalManager(userModel);
}

我的 UserPrincipalManager :

public class UserPrincipalManager implements UserDetails {

    private static final long serialVersionUID = 7464059818443209139L;

    private UserModel userModel;
    private ProjectModel projectModel;

    @Getter @Setter
    private String userId;

    @Autowired
    public UserPrincipalManager(UserModel userModel) {
        this.userModel = userModel;
        this.userId = userModel.getUserId();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new HashSet<>();
        Collection<AuthorityModel> authorityModelEntities = new HashSet<>();
        // get user roles
        Collection<RoleModel> roleModels = userModel.getRoles();
        if (roleModels == null) {
            return authorities; // null
        }
        // get user roles
        roleModels.forEach((role) ->{
            authorities.add(new SimpleGrantedAuthority(role.getName()));
            authorityModelEntities.addAll(role.getAuthorities());
        });
        // get user authorities
        authorityModelEntities.forEach(authorityModel -> {
            authorities.add(new SimpleGrantedAuthority(authorityModel.getName()));
        });

        return authorities;
    }

    @Override
    public String getPassword() {
        return this.userModel.getEncryptedPassword();
    }

    @Override
    public String getUsername() {
        return this.userModel.getEmail();
    }

    // we do not store this information in DB
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // we do not store this information in DB (yet)
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // we do not store this information in DB (yet)
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // isEnabled depending if account is activated => email verification status value
    @Override
    public boolean isEnabled() {
        return this.userModel.getEmailVerificationStatus();
    }

}

在尝试登录时,用户 sql 请求正在循环。

at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:59)
    at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:31)
    at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:303)
    at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:110)
    at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:242)
    at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:188)
    at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:152)
    at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:106)
    at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:59)
    at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:31)
    at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:303)
    at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:110)
    at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:242)
    at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:188)
    at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:152)
    at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:106)
    at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:59)
    at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:31)
    at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:303)
    at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:110)
    at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:242)
    at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:188)
    at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:152)
    at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:106)

最终应用程序崩溃并返回 403 错误。

2020-10-05 12:07:22.215 DEBUG 4564 --- [nio-8080-exec-8] o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is anonymous); redirecting to authentication entry point

org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.3.3.RELEASE.jar:5.3.3.RELEASE]

如果用户没有关联项目,则登录功能有效。

【问题讨论】:

    标签: spring-boot hibernate jpa spring-security modelmapper


    【解决方案1】:

    我对模型映射器一无所知,但我想为您提供一个替代解决方案,因为我认为这是 Blaze-Persistence Entity Views 的完美用例。

    我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

    使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:

    @EntityView(UserModel.class)
    public interface UserDto extends Serializable {
        @IdMapping
        Long getId();
        String getUserId();
        String getFirstName();
        String getLastName();
        String getEmail();
        String getPassword();
        String getEncryptedPassword();
        String getEmailVerificationToken();
        Boolean getEmailVerificationStatus();
        Set<String> getRoles();
        Set<ProjectDto> getProjects();
    
        @EntityView(ProjectModel.class)
        interface ProjectDto {
            @IdMapping
            Long getId();
            String getProjectId();
            // Other mappings...
        }
    }
    

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

    UserDto a = entityViewManager.find(entityManager, UserDto.class, id);

    Spring Data 集成让您可以像使用 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

    这里最大的好处是,它只会获取实际需要的列,并在启动时根据您的 JPA 模型验证 DTO 模型,因此不会再出现运行时意外!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-19
      • 2013-06-19
      • 2011-06-25
      • 1970-01-01
      • 2021-06-25
      • 2012-04-19
      • 2013-01-29
      相关资源
      最近更新 更多