24

アプリケーションでは、Hibernate / JPA、Spring、Spring Data、およびSpringSecurityを使用します。JPAを使用してマッピングされる標準Userエンティティがあります。さらに、私はUserRepository

public interface UserRepository extends CrudRepository<User, Long> {
    List<User> findByUsername(String username);
}

これは、クエリメソッドの命名に関するSpringDataの規則に従います。私は実体を持っています

@Entity
public class Foo extends AbstractAuditable<User, Long> {
    private String name;
}

SpringData監査サポートを使用したい。(ここで説明します。)したがってAuditorService、次のように作成しました。

@Service
public class AuditorService implements AuditorAware<User> {

    private UserRepository userRepository;

    @Override
    public User getCurrentAuditor() {
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        List<User> users = userRepository.findByUsername(username);
        if (users.size() > 0) {
            return users.get(0);
        } else {
            throw new IllegalArgumentException();
        }
    }

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

メソッドを作成するとき

@Transactional
public void createFoo() {
    Foo bar = new Foo(); 
    fooRepository.save(foo);
}

すべてが正しく配線されてFooRepositoryおり、 SpringDataである場合CrudRepository。次に、への呼び出しが休止状態をトリガーしてデータベースにデータをフラッシュするようStackOverflowErrorに見えるため、aがスローされます。これにより、 who呼び出しがトリガーされ、再びフラッシュがトリガーされます。findByUsernameAuditingEntityListenerAuditorService#getCurrentAuditor

この再帰を回避する方法は?Userエンティティをロードするための「標準的な方法」はありますか?または、Hibernate / JPAがフラッシュするのを防ぐ方法はありますか?

4

4 に答える 4

21

解決策は、実装でUserレコードをフェッチしないことです。AuditorAware選択クエリがフラッシュをトリガーするため(これは、Hibernate / JPAがデータベースにデータを書き込んで、選択を実行する前にトランザクションをコミットするため)、への呼び出しをトリガーするため、これにより説明されたループがトリガーされますAuditorAware#getCurrentAuditor

解決策は、SpringSecurityに提供されたUserレコードを保存することです。UserDetailsしたがって、私は独自の実装を作成しました。

public class UserAwareUserDetails implements UserDetails {

    private final User user;
    private final Collection<? extends GrantedAuthority> grantedAuthorities;

    public UserAwareUserDetails(User user) {
        this(user, new ArrayList<GrantedAuthority>());
    }

    public UserAwareUserDetails(User user, Collection<? extends GrantedAuthority> grantedAuthorities) {
        this.user = user;
        this.grantedAuthorities = grantedAuthorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return grantedAuthorities;
    }

    @Override
    public String getPassword() {
        return user.getSaltedPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public User getUser() {
        return user;
    }
}

さらに、UserDetailsServiceをロードしUserて作成するように変更しましUserAwareUserDetailsた。これで、 :Userを介してインスタンスにアクセスできるようになりました。SercurityContextHolder

@Override
public User getCurrentAuditor() {
    return ((UserAwareUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
}
于 2013-01-09T10:00:34.373 に答える
21

同じ問題が発生し、findByUsername(username)メソッドの伝播をに変更するだけPropagation.REQUIRES_NEWでした。これはトランザクションの問題であると思われたため、新しいトランザクションを使用するように変更しました。これはうまくいきました。これがお役に立てば幸いです。

@Repository
public interface UserRepository extends JpaRepository<User, String> {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    List<User> findByUsername(String username);
}
于 2015-08-22T12:06:48.367 に答える
3

ユーザーエンティティを2つの異なる目的で使用しているようです。

  • 認証
  • 監査

監査目的で特別なAuditableUserを準備する方が良いと思います(元のUserと同じユーザー名フィールドがあります)。次の場合を考えてみましょう。データベースから一部のユーザーを削除したい。すべての監査オブジェクトがユーザーにリンクされている場合、それらはa)作成者を失いますb)カスケードによっても削除される可能性があります(リンクの実装方法によって異なります)。あなたがそれを望んでいるかどうかわからない。したがって、特別なAuditableUserを使用すると、次のようになります。

  • 再帰なし
  • システムから一部のユーザーを削除し、それに関するすべての監査情報を保持する機能
于 2013-01-09T10:12:35.430 に答える
3

正直なところ、あなたは実際にはお互いの実体を必要としません。たとえば、同様の問題が発生し、次の方法で解決しました。

public class SpringSecurityAuditorAware implements AuditorAware<SUser>, ApplicationListener<ContextRefreshedEvent> {
    private static final Logger LOGGER = getLogger(SpringSecurityAuditorAware.class);
    @Autowired
    SUserRepository repository;
    private SUser systemUser;

    @Override
    public SUser getCurrentAuditor() {
        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        SUser principal;
        if (authentication == null || !authentication.isAuthenticated()) {
            principal = systemUser;
        } else {
            principal = (SUser) authentication.getPrincipal();
        }
        LOGGER.info(String.format("Current auditor is >>> %s", principal));
        return principal;
    }

    @Override
    public void onApplicationEvent(final ContextRefreshedEvent event) {
        if (this.systemUser == null) {
            LOGGER.info("%s >>> loading system user");
            systemUser = this.repository.findOne(QSUser.sUser.credentials.login.eq("SYSTEM"));
        }
    }
}

ここで、SUserは、監査とセキュリティの両方に使用するクラスです。私はおそらくあなたとは異なるユースケースを持っていて、私のアプローチは後で削除されますが、このように解決することができます。

于 2013-07-21T00:23:19.157 に答える