KEYCLOAK-6589: Optimize jpql in User search API

This commit removes the 6 n+1 select
that are issued when calling GET /users api.

We now have 4 select queries.
This commit is contained in:
gonzalad 2018-02-13 11:06:06 +01:00 committed by Hynek Mlnařík
parent 9ef1f1b73c
commit 898347366d
2 changed files with 35 additions and 5 deletions

View file

@ -59,6 +59,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -945,6 +946,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
UserEntity userRef = em.getReference(UserEntity.class, user.getId()); UserEntity userRef = em.getReference(UserEntity.class, user.getId());
entity.setUser(userRef); entity.setUser(userRef);
em.persist(entity); em.persist(entity);
MultivaluedHashMap<String, String> config = cred.getConfig(); MultivaluedHashMap<String, String> config = cred.getConfig();
if (config != null && !config.isEmpty()) { if (config != null && !config.isEmpty()) {
@ -962,6 +964,11 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
} }
} }
UserEntity userEntity = userInEntityManagerContext(user.getId());
if (userEntity != null) {
userEntity.getCredentials().add(entity);
}
return toModel(entity); return toModel(entity);
} }
@ -970,6 +977,10 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
CredentialEntity entity = em.find(CredentialEntity.class, id); CredentialEntity entity = em.find(CredentialEntity.class, id);
if (entity == null) return false; if (entity == null) return false;
em.remove(entity); em.remove(entity);
UserEntity userEntity = userInEntityManagerContext(user.getId());
if (userEntity != null) {
userEntity.getCredentials().remove(entity);
}
return true; return true;
} }
@ -1017,11 +1028,19 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
@Override @Override
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
UserEntity userEntity = em.getReference(UserEntity.class, user.getId()); List<CredentialEntity> results;
TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class) UserEntity userEntity = userInEntityManagerContext(user.getId());
.setParameter("type", type) if (userEntity != null) {
.setParameter("user", userEntity);
List<CredentialEntity> results = query.getResultList(); // user already in persistence context, no need to execute a query
results = userEntity.getCredentials().stream().filter(it -> it.getType().equals(type)).collect(Collectors.toList());
} else {
userEntity = em.getReference(UserEntity.class, user.getId());
TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class)
.setParameter("type", type)
.setParameter("user", userEntity);
results = query.getResultList();
}
List<CredentialModel> rtn = new LinkedList<>(); List<CredentialModel> rtn = new LinkedList<>();
for (CredentialEntity entity : results) { for (CredentialEntity entity : results) {
rtn.add(toModel(entity)); rtn.add(toModel(entity));
@ -1062,4 +1081,10 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
em.persist(user); em.persist(user);
} }
} }
private UserEntity userInEntityManagerContext(String id) {
UserEntity user = em.getReference(UserEntity.class, id);
boolean isLoaded = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(user);
return isLoaded ? user : null;
}
} }

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities; package org.keycloak.models.jpa.entities;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.Access; import javax.persistence.Access;
@ -90,12 +92,15 @@ public class UserEntity {
protected String realmId; protected String realmId;
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user") @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
@Fetch(FetchMode.SUBSELECT)
protected Collection<UserAttributeEntity> attributes = new ArrayList<UserAttributeEntity>(); protected Collection<UserAttributeEntity> attributes = new ArrayList<UserAttributeEntity>();
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user") @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
@Fetch(FetchMode.SUBSELECT)
protected Collection<UserRequiredActionEntity> requiredActions = new ArrayList<UserRequiredActionEntity>(); protected Collection<UserRequiredActionEntity> requiredActions = new ArrayList<UserRequiredActionEntity>();
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user") @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
@Fetch(FetchMode.SUBSELECT)
protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>(); protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
@Column(name="FEDERATION_LINK") @Column(name="FEDERATION_LINK")