keycloak-uncached

Changes

Details

diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
index fa233bb..d4ae6e7 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
@@ -2,31 +2,46 @@ package org.keycloak.models.jpa.entities;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
+import javax.persistence.FetchType;
 import javax.persistence.GeneratedValue;
 import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToOne;
 
 import org.hibernate.annotations.GenericGenerator;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
+@NamedQueries({
+        @NamedQuery(name="deleteAuthenticationLinksByRealm", query="delete from AuthenticationLinkEntity authLink where authLink.user IN (select u from UserEntity u where realm=:realm)")
+})
 @Entity
 public class AuthenticationLinkEntity {
 
     @Id
-    @Column(length = 36)
-    @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
-    @GeneratedValue(generator = "keycloak_generator")
-    private String id;
+    @GeneratedValue
+    protected long id;
 
     protected String authProvider;
     protected String authUserId;
 
-    public String getId() {
+    // NOTE: @OnetoOne creates a constraint race condition if the join column is on AuthenticationLinkEntity.
+    // The race is that user gets loaded concurrently, creates link concurrently, and sets it.  Therefore, we have
+    // a @ManyToOne on both sides.  Broken yes, but, I think we're going to replace AuthenticationLinkEntity anyways.
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name="userId")
+    protected UserEntity user;
+
+
+    public long getId() {
         return id;
     }
 
-    public void setId(String id) {
+    public void setId(long id) {
         this.id = id;
     }
 
@@ -45,4 +60,12 @@ public class AuthenticationLinkEntity {
     public void setAuthUserId(String authUserId) {
         this.authUserId = authUserId;
     }
+
+    public UserEntity getUser() {
+        return user;
+    }
+
+    public void setUser(UserEntity user) {
+        this.user = user;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java
index 1efd456..abd19f5 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java
@@ -21,10 +21,8 @@ import java.util.Map;
 public class AuthenticationProviderEntity {
 
     @Id
-    @Column(length = 36)
-    @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
-    @GeneratedValue(generator = "keycloak_generator")
-    protected String id;
+    @GeneratedValue
+    protected long id;
 
     private String providerName;
     private boolean passwordUpdateSupported;
@@ -38,11 +36,11 @@ public class AuthenticationProviderEntity {
     })
     private Map<String, String> config;
 
-    public String getId() {
+    public long getId() {
         return id;
     }
 
-    public void setId(String id) {
+    public void setId(long id) {
         this.id = id;
     }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
index 7182433..2592cf2 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
@@ -5,6 +5,7 @@ import javax.persistence.Entity;
 import javax.persistence.FetchType;
 import javax.persistence.GeneratedValue;
 import javax.persistence.Id;
+import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
@@ -16,15 +17,15 @@ import org.hibernate.annotations.GenericGenerator;
  * @version $Revision: 1 $
  */
 @NamedQueries({
-        @NamedQuery(name="credentialByUserAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type")
+        @NamedQuery(name="credentialByUserAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type"),
+        @NamedQuery(name="deleteCredentialsByRealm", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where realm=:realm)")
+
 })
 @Entity
 public class CredentialEntity {
     @Id
-    @Column(length = 36)
-    @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
-    @GeneratedValue(generator = "keycloak_generator")
-    protected String id;
+    @GeneratedValue
+    protected long id;
 
     protected String type;
     protected String value;
@@ -33,10 +34,16 @@ public class CredentialEntity {
     protected int hashIterations;
 
     @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name="userId")
     protected UserEntity user;
 
-    @ManyToOne(fetch = FetchType.LAZY)
-    protected RealmEntity realm;
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
 
     public String getValue() {
         return value;
@@ -54,14 +61,6 @@ public class CredentialEntity {
         this.type = type;
     }
 
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-
     public String getDevice() {
         return device;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredCredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredCredentialEntity.java
index 3214e0b..12c83af 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredCredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredCredentialEntity.java
@@ -14,21 +14,19 @@ import org.hibernate.annotations.GenericGenerator;
 @Entity
 public class RequiredCredentialEntity {
     @Id
-    @Column(length = 36)
-    @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
-    @GeneratedValue(generator = "keycloak_generator")
-    protected String id;
+    @GeneratedValue
+    protected long id;
 
     protected String type;
     protected boolean input;
     protected boolean secret;
     protected String formLabel;
 
-    public String getId() {
+    public long getId() {
         return id;
     }
 
-    public void setId(String id) {
+    public void setId(long id) {
         this.id = id;
     }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ScopeMappingEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ScopeMappingEntity.java
index 6972e43..83588ef 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ScopeMappingEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ScopeMappingEntity.java
@@ -24,10 +24,9 @@ import javax.persistence.NamedQuery;
 @Entity
 public class ScopeMappingEntity {
     @Id
-    @Column(length = 36)
-    @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
-    @GeneratedValue(generator = "keycloak_generator")
-    protected String id;
+    @GeneratedValue
+    protected long id;
+
     @ManyToOne(fetch= FetchType.LAZY)
     protected ClientEntity client;
 
@@ -35,11 +34,11 @@ public class ScopeMappingEntity {
     @JoinColumn(name="roleId")
     protected RoleEntity role;
 
-    public String getId() {
+    public long getId() {
         return id;
     }
 
-    public void setId(String id) {
+    public void setId(long id) {
         this.id = id;
     }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java
index ece95bb..b3fe6e6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java
@@ -5,6 +5,7 @@ import javax.persistence.Entity;
 import javax.persistence.FetchType;
 import javax.persistence.GeneratedValue;
 import javax.persistence.Id;
+import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
@@ -18,15 +19,14 @@ import org.hibernate.annotations.GenericGenerator;
 @NamedQueries({
         @NamedQuery(name="findSocialLinkByUser", query="select link from SocialLinkEntity link where link.user = :user"),
         @NamedQuery(name="findSocialLinkByUserAndProvider", query="select link from SocialLinkEntity link where link.user = :user and link.socialProvider = :socialProvider"),
-        @NamedQuery(name="findUserByLinkAndRealm", query="select link.user from SocialLinkEntity link where link.realm = :realm and link.socialProvider = :socialProvider and link.socialUserId = :socialUserId")
+        @NamedQuery(name="findUserByLinkAndRealm", query="select link.user from SocialLinkEntity link where link.realm = :realm and link.socialProvider = :socialProvider and link.socialUserId = :socialUserId"),
+        @NamedQuery(name="deleteSocialLinkByRealm", query="delete from SocialLinkEntity social where social.user IN (select u from UserEntity u where realm=:realm)")
 })
 @Entity
 public class SocialLinkEntity {
     @Id
-    @Column(length = 36)
-    @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
-    @GeneratedValue(generator = "keycloak_generator")
-    private String id;
+    @GeneratedValue
+    protected long id;
 
     @ManyToOne(fetch = FetchType.LAZY)
     private UserEntity user;
@@ -38,11 +38,11 @@ public class SocialLinkEntity {
     protected String socialUserId;
     protected String socialUsername;
 
-    public String getId() {
+    public long getId() {
         return id;
     }
 
-    public void setId(String id) {
+    public void setId(long id) {
         this.id = id;
     }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
new file mode 100755
index 0000000..584dae0
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
@@ -0,0 +1,66 @@
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.MapsId;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="deleteUserAttributesByRealm", query="delete from  UserAttributeEntity attr where attr.user IN (select u from UserEntity u where realm=:realm)")
+})
+@Entity
+public class UserAttributeEntity {
+    @Id
+    @GeneratedValue
+    protected long id;
+
+    @ManyToOne(fetch= FetchType.LAZY)
+    protected UserEntity user;
+
+    protected String name;
+    protected String value;
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public UserEntity getUser() {
+        return user;
+    }
+
+    public void setUser(UserEntity user) {
+        this.user = user;
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index 2329980..feb93cc 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -35,7 +35,8 @@ import java.util.Set;
         @NamedQuery(name="getRealmUserByUsername", query="select u from UserEntity u where u.username = :username and u.realm = :realm"),
         @NamedQuery(name="getRealmUserByEmail", query="select u from UserEntity u where u.email = :email and u.realm = :realm"),
         @NamedQuery(name="getRealmUserByLastName", query="select u from UserEntity u where u.lastName = :lastName and u.realm = :realm"),
-        @NamedQuery(name="getRealmUserByFirstLastName", query="select u from UserEntity u where u.firstName = :first and u.lastName = :last and u.realm = :realm")
+        @NamedQuery(name="getRealmUserByFirstLastName", query="select u from UserEntity u where u.firstName = :first and u.lastName = :last and u.realm = :realm"),
+        @NamedQuery(name="deleteUsersByRealm", query="delete from UserEntity u where u.realm = :realm")
 })
 @Entity
 @Table(uniqueConstraints = {
@@ -62,20 +63,17 @@ public class UserEntity {
     @JoinColumn(name = "realm")
     protected RealmEntity realm;
 
-    @ElementCollection
-    @MapKeyColumn(name="name")
-    @Column(name="value")
-    @CollectionTable
-    protected Map<String, String> attributes = new HashMap<String, String>();
+    @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
+    protected Collection<UserAttributeEntity> attributes = new ArrayList<UserAttributeEntity>();
 
-    @ElementCollection
-    @CollectionTable
-    protected Set<UserModel.RequiredAction> requiredActions = new HashSet<UserModel.RequiredAction>();
+    @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
+    protected Collection<UserRequiredActionEntity> requiredActions = new ArrayList<UserRequiredActionEntity>();
 
-    @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
+    @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
     protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
 
-    @OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
+    @ManyToOne
+    @JoinColumn(name="link_id")
     protected AuthenticationLinkEntity authenticationLink;
 
     public String getId() {
@@ -151,19 +149,19 @@ public class UserEntity {
         this.emailVerified = emailVerified;
     }
 
-    public Map<String, String> getAttributes() {
+    public Collection<UserAttributeEntity> getAttributes() {
         return attributes;
     }
 
-    public void setAttributes(Map<String, String> attributes) {
+    public void setAttributes(Collection<UserAttributeEntity> attributes) {
         this.attributes = attributes;
     }
 
-    public Set<UserModel.RequiredAction> getRequiredActions() {
+    public Collection<UserRequiredActionEntity> getRequiredActions() {
         return requiredActions;
     }
 
-    public void setRequiredActions(Set<UserModel.RequiredAction> requiredActions) {
+    public void setRequiredActions(Collection<UserRequiredActionEntity> requiredActions) {
         this.requiredActions = requiredActions;
     }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java
new file mode 100755
index 0000000..21f2e5b
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java
@@ -0,0 +1,53 @@
+package org.keycloak.models.jpa.entities;
+
+import org.keycloak.models.UserModel;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="deleteUserRequiredActionsByRealm", query="delete from UserRequiredActionEntity action where action.user IN (select u from UserEntity u where realm=:realm)")
+})
+@Entity
+public class UserRequiredActionEntity {
+    @Id
+    @GeneratedValue
+    protected long id;
+
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name="userId")
+    protected UserEntity user;
+
+    protected UserModel.RequiredAction action;
+
+    public long getId() {
+        return id;
+    }
+
+    public UserModel.RequiredAction getAction() {
+        return action;
+    }
+
+    public void setAction(UserModel.RequiredAction action) {
+        this.action = action;
+    }
+
+    public UserEntity getUser() {
+        return user;
+    }
+
+    public void setUser(UserEntity user) {
+        this.user = user;
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java
index 54b8a47..5cbe55b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java
@@ -19,27 +19,29 @@ import org.hibernate.annotations.GenericGenerator;
 @NamedQueries({
         @NamedQuery(name="userHasRole", query="select m from UserRoleMappingEntity m where m.user = :user and m.role = :role"),
         @NamedQuery(name="userRoleMappings", query="select m from UserRoleMappingEntity m where m.user = :user"),
-        @NamedQuery(name="userRoleMappingIds", query="select m.role.id from UserRoleMappingEntity m where m.user = :user")
+        @NamedQuery(name="userRoleMappingIds", query="select m.role.id from UserRoleMappingEntity m where m.user = :user"),
+        @NamedQuery(name="deleteUserRoleMappingsByRealm", query="delete from  UserRoleMappingEntity mapping where mapping.user IN (select u from UserEntity u where realm=:realm)")
+
 })
 @Entity
 public class UserRoleMappingEntity  {
     @Id
-    @Column(length = 36)
-    @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
-    @GeneratedValue(generator = "keycloak_generator")
-    protected String id;
+    @GeneratedValue
+    protected long id;
+
     @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name="userId")
     protected UserEntity user;
 
     @ManyToOne(fetch= FetchType.LAZY)
     @JoinColumn(name="roleId")
     protected RoleEntity role;
 
-    public String getId() {
+    public long getId() {
         return id;
     }
 
-    public void setId(String id) {
+    public void setId(long id) {
         this.id = id;
     }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index db947e2..5ea1a82 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -8,6 +8,7 @@ import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserProvider;
+import org.keycloak.models.jpa.entities.CredentialEntity;
 import org.keycloak.models.jpa.entities.RealmEntity;
 import org.keycloak.models.jpa.entities.RoleEntity;
 import org.keycloak.models.jpa.entities.SocialLinkEntity;
@@ -124,9 +125,18 @@ public class JpaUserProvider implements UserProvider {
     @Override
     public void preRemove(RealmModel realm) {
         RealmEntity realmEntity = em.getReference(RealmEntity.class, realm.getId());
-        for (UserEntity u : em.createQuery("from UserEntity u where u.realm = :realm", UserEntity.class).setParameter("realm", realmEntity).getResultList()) {
-            removeUser(realm, u.getUsername());
-        }
+        int num = em.createNamedQuery("deleteUserRoleMappingsByRealm")
+                .setParameter("realm", realmEntity).executeUpdate();
+        num = em.createNamedQuery("deleteSocialLinkByRealm")
+                .setParameter("realm", realmEntity).executeUpdate();
+        num = em.createNamedQuery("deleteCredentialsByRealm")
+                .setParameter("realm", realmEntity).executeUpdate();
+        num = em.createNamedQuery("deleteUserAttributesByRealm")
+                .setParameter("realm", realmEntity).executeUpdate();
+        num = em.createNamedQuery("deleteAuthenticationLinksByRealm")
+                .setParameter("realm", realmEntity).executeUpdate();
+        num = em.createNamedQuery("deleteUsersByRealm")
+                .setParameter("realm", realmEntity).executeUpdate();
     }
 
     @Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index fc2fcbc..9543604 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -12,7 +12,9 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.jpa.entities.AuthenticationLinkEntity;
 import org.keycloak.models.jpa.entities.CredentialEntity;
 import org.keycloak.models.jpa.entities.RoleEntity;
+import org.keycloak.models.jpa.entities.UserAttributeEntity;
 import org.keycloak.models.jpa.entities.UserEntity;
+import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
 import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
 import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
 
@@ -21,6 +23,7 @@ import javax.persistence.TypedQuery;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -79,52 +82,84 @@ public class UserAdapter implements UserModel {
 
     @Override
     public void setAttribute(String name, String value) {
-        Map<String, String> attributes = user.getAttributes();
-        if (attributes == null) {
-            attributes = new HashMap<String, String>();
+        for (UserAttributeEntity attr : user.getAttributes()) {
+            if (attr.getName().equals(name)) {
+                attr.setValue(value);
+                return;
+            }
         }
-        attributes.put(name, value);
-        user.setAttributes(attributes);
+        UserAttributeEntity attr = new UserAttributeEntity();
+        attr.setName(name);
+        attr.setValue(value);
+        attr.setUser(user);
+        em.persist(attr);
+        user.getAttributes().add(attr);
     }
 
     @Override
     public void removeAttribute(String name) {
-        Map<String, String> attributes = user.getAttributes();
-        if (attributes == null) {
-            attributes = new HashMap<String, String>();
+        Iterator<UserAttributeEntity> it = user.getAttributes().iterator();
+        while (it.hasNext()) {
+            UserAttributeEntity attr = it.next();
+            if (attr.getName().equals(name)) {
+                it.remove();
+                em.remove(attr);
+            }
         }
-        attributes.remove(name);
-        user.setAttributes(attributes);
     }
 
     @Override
     public String getAttribute(String name) {
-        if (user.getAttributes() == null) return null;
-        return user.getAttributes().get(name);
+        for (UserAttributeEntity attr : user.getAttributes()) {
+            if (attr.getName().equals(name)) {
+                return attr.getValue();
+            }
+        }
+        return null;
     }
 
     @Override
     public Map<String, String> getAttributes() {
         Map<String, String> result = new HashMap<String, String>();
-        result.putAll(user.getAttributes());
+        for (UserAttributeEntity attr : user.getAttributes()) {
+            result.put(attr.getName(), attr.getValue());
+        }
         return result;
     }
 
     @Override
     public Set<RequiredAction> getRequiredActions() {
         Set<RequiredAction> result = new HashSet<RequiredAction>();
-        result.addAll(user.getRequiredActions());
+        for (UserRequiredActionEntity attr : user.getRequiredActions()) {
+            result.add(attr.getAction());
+        }
         return result;
     }
 
     @Override
     public void addRequiredAction(RequiredAction action) {
-        user.getRequiredActions().add(action);
+        for (UserRequiredActionEntity attr : user.getRequiredActions()) {
+            if (attr.getAction().equals(action)) {
+                return;
+            }
+        }
+        UserRequiredActionEntity attr = new UserRequiredActionEntity();
+        attr.setAction(action);
+        attr.setUser(user);
+        em.persist(attr);
+        user.getRequiredActions().add(attr);
     }
 
     @Override
     public void removeRequiredAction(RequiredAction action) {
-        user.getRequiredActions().remove(action);
+        Iterator<UserRequiredActionEntity> it = user.getRequiredActions().iterator();
+        while (it.hasNext()) {
+            UserRequiredActionEntity attr = it.next();
+            if (attr.getAction().equals(action)) {
+                it.remove();
+                em.remove(attr);
+            }
+        }
     }
 
 
@@ -357,10 +392,17 @@ public class UserAdapter implements UserModel {
         AuthenticationLinkEntity entity = new AuthenticationLinkEntity();
         entity.setAuthProvider(authenticationLink.getAuthProvider());
         entity.setAuthUserId(authenticationLink.getAuthUserId());
-
-        user.setAuthenticationLink(entity);
+        entity.setUser(user);
+
+        if (user.getAuthenticationLink() != null) {
+            AuthenticationLinkEntity old = user.getAuthenticationLink();
+            old.setUser(null);
+            em.remove(old);
+            user.setAuthenticationLink(null);
+            em.flush();
+        }
         em.persist(entity);
-        em.persist(user);
+        user.setAuthenticationLink(entity);
         em.flush();
     }
 
diff --git a/model/jpa/src/test/resources/META-INF/persistence.xml b/model/jpa/src/test/resources/META-INF/persistence.xml
index 7391d71..8b3c6d7 100755
--- a/model/jpa/src/test/resources/META-INF/persistence.xml
+++ b/model/jpa/src/test/resources/META-INF/persistence.xml
@@ -15,6 +15,8 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
 
diff --git a/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml b/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml
index 294edb4..f041f73 100755
--- a/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml
+++ b/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml
@@ -14,6 +14,8 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
 
diff --git a/server/src/main/resources/META-INF/persistence.xml b/server/src/main/resources/META-INF/persistence.xml
index 294edb4..feb9d2d 100755
--- a/server/src/main/resources/META-INF/persistence.xml
+++ b/server/src/main/resources/META-INF/persistence.xml
@@ -14,6 +14,8 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
 
diff --git a/testsuite/integration/src/main/resources/META-INF/persistence.xml b/testsuite/integration/src/main/resources/META-INF/persistence.xml
index 70544c6..9c04a33 100755
--- a/testsuite/integration/src/main/resources/META-INF/persistence.xml
+++ b/testsuite/integration/src/main/resources/META-INF/persistence.xml
@@ -15,6 +15,8 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
         <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
 
diff --git a/testsuite/performance/src/test/resources/META-INF/persistence.xml b/testsuite/performance/src/test/resources/META-INF/persistence.xml
index 87857f0..4850a25 100755
--- a/testsuite/performance/src/test/resources/META-INF/persistence.xml
+++ b/testsuite/performance/src/test/resources/META-INF/persistence.xml
@@ -15,6 +15,8 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
         <class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
         <class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
         <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
diff --git a/testsuite/tools/src/main/resources/META-INF/persistence.xml b/testsuite/tools/src/main/resources/META-INF/persistence.xml
index ee721fe..4e3d279 100755
--- a/testsuite/tools/src/main/resources/META-INF/persistence.xml
+++ b/testsuite/tools/src/main/resources/META-INF/persistence.xml
@@ -14,6 +14,8 @@
         <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
         <class>org.keycloak.models.jpa.entities.UserEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
         <class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
         <class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
         <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>