Ebean is not cascading persistence with Play-2.5(OneToMany) - postgresql

Here are my models.
User:
#Entity
#Table(name="users")
public class User extends Model {
String username;
#Id
String id;
#OneToMany(cascade = CascadeType.ALL)
List<Tag> tags;
}
Tag:
#Entity
#Table(name="tags")
public class Tag extends Model {
#Constraints.Required
public String tag;
}
Persistence code(Removed unnecessary code):
User user = new User();
user.id = UUID.randomUUID().toString();
user.username = username; // String
user.tags = tags; // list of tags;
Ebean.save(user);
I am calling Ebean.save(user) after adding tags to user object.
Tags added on user are not persisted to database. I am also not seeing any exception, other fields of user get persisted but not tags.
Am I missing something?
Note: I am using postgres.

Thanks for the suggestion #marcospereira.
I was missing id field in Tag model. After enabling debugging and sql logging I noticed warning in logs.
The correct way to create Tag class:
#Entity
#Table(name="tags")
public class Tag {
#Id
#GeneratedValue
public String id;
public String tag;
}
but It is weird why Ebean is doing that.
Hope this helps someone in future.

Related

Spring Data JPA auditing fails when persisting detached entity

I've setup JPA auditing with Spring Data JPA AuditingEntityListener and AuditorAware bean. What I want is to be able to persist auditor details even on entities with predefined identifiers.
The problem is that when JPA entity with predefined id is being persisted and flushed it's auditor details cannot be persisted:
object references an unsaved transient instance - save the transient instance before flushing: me.auditing.dao.AuditorDetails
The interesting part is that when an entity with a generated id is saved - everything's fine. In both cases the entities are new. I could not pinpoint the problem digging through hibernate code so I've created a sample project to demonstrate this (test class me.auditing.dao.AuditedEntityIntegrationTest) It has both entities with predefined and generated identifiers and should be audited.
The entities are:
#Entity
public class AuditedEntityWithPredefinedId extends AuditableEntity {
#Id
private String id;
public String getId() {
return id;
}
public AuditedEntityWithPredefinedId setId(String id) {
this.id = id;
return this;
}
}
and:
#Entity
public class AuditedEntityWithGeneratedId extends AuditableEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
public String getId() {
return id;
}
public AuditedEntityWithGeneratedId setId(String id) {
this.id = id;
return this;
}
}
where parent class is:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class AuditableEntity implements Serializable {
private static final long serialVersionUID = -7541732975935355789L;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#CreatedBy
private AuditorDetails createdBy;
#CreatedDate
private LocalDateTime createdDate;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#LastModifiedBy
private AuditorDetails modifiedBy;
#LastModifiedDate
private LocalDateTime modifiedDate;
And the auditor getter implementation is:
#Override
public AuditorDetails getCurrentAuditor() {
return new AuditorDetails()
.setId(null)
.setUserId("someUserId")
.setDivisionId("someDivisionId");
}
Edit 2016-08-08: It seems that when a new entity with predefined id is saved, it gets two different instances of createdBy and modifiedBy AuditorDetails, which is quite logical if the entity wouldn't be actually new. So, a completely new entity with generated gets both AuditorDetails of same instance, and the one with manually set id doesn't. I tested it by saving auditor details in AuditorAware bean before returning it to AuditingHandler.
Ok, so for now the only solution I could find is to actually persist AuditorDetails before writing it to audited entities like so:
#Override
#Transactional
public AuditorDetails getCurrentAuditor() {
AuditorDetails details = new AuditorDetails()
.setId(null)
.setUserId("someUserId")
.setDivisionId("someDivisionId");
return auditorDetailsRepository.save(details);
}
It is not the most elegant solution, but it works for now.

spring data jpa fine granular auditing, custom audit

I have requirement where I need to insert user name and group name to which the user belongs (both available in SecurityContext) in the same table.
class Entity
{
#createdBy
String username
#createdBy
String groupname
other fields ...
}
As per requirement. I cant solve this issue by making a user class and referencing it through a foreign key.
With current implementation of AuditingHandler both fields are getting the same value. How do I make sure they get respective values.
Can this be achieved using current implementation ?
If not thn how can I provide custom implementation of AuditingHandler ?
You could make a separate embeddable class and annotate it with #CreatedBy in your parent class. One way is to define a bean implementing AuditorAware, then you can make it return custom object, containing your two required fields. For example, your parent class would look like this (note the listener annotation):
#Entity
#EntityListeners(AuditingEntityListener.class)
public class AuditedEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
#Embedded
#CreatedBy
private AuditorDetails createdBy;
// setters and getters
}
where AuditorDetails is:
#Embeddable
public class AuditorDetails {
private String username;
private String groupname;
// setters and getters
}
and finally, your AuditorAware bean:
#Component
class AuditorAwareImpl implements AuditorAware<AuditorDetails> {
#Override
public AuditorDetails getCurrentAuditor() {
return new AuditorDetails()
.setUsername("someUser")
.setGroupname("someGroup");
}
}
AuditingHandler fetches your custom AuditorDetails from your AuditorAware bean (it must be single bean implementing it) and sets it in your auditable entity.

How to map existing JPA entities to PicketLink

I am trying to migrate a Seam 2 app to CDI and use PicketLink for security. After all the reading and researching, it seems like all the examples are having one to one mapping between PicketLink model and the backend entity. e.g. Account to AccountEntity, Partition to PartitionEntity. Since I already have entities in place representing identity model, I am stuck on trying to map them to PicketLink. Here is what I have:
#MappedSuperClass
public class ModelEntityBase implement Serializable {
#Id #Generated
Long id;
Date creationDate;
}
#Entity
public Account extends ModelEntityBase {
String username;
String passwordHash;
#OneToOne(mappedBy = "account")
Person person;
}
#Entity
public Person extends ModelEntityBase {
String name;
String email;
#OneToOne
#JoinColumn(name = "account_id")
Account account;
}
Two entities (plus a super class) representing a single identity model in PicketLink, e.g. stereo type User.
Based on this why IdentityType id is String not Long, I tried to add a new Entity in:
#Entity
#IdentityManaged(BaseIdentityType.class);
public class IdentityTypeEntity implement Serializble {
#Id #Identifier
private String id;
#OneToOne(optional = false, mappedBy = "identityType")
#OwnerReference
private Account account;
#IdentityClass
private String typeName;
#ManyToOne #OwnerReference
private PartitionEntity partition;
}
I've tried a few different ways with the annotation and model classes. But when using IdentityManager.add(myUserModel), I just can't get it to populate all the entities. Is this even possible?
Got help from Pedro (PicketLink Dev). Post the answer here to help others.
This is the model class I ended up using.
#IdentityStereotype(USER)
public class User extends AbstractAttributedType implements Account {
#AttributeProperty
private Account accountEntity;
#AttributeProperty
#StereotypeProperty(IDENTITY_USER_NAME)
#Unique
private String username;
#AttributeProperty
private boolean enabled;
#AttributeProperty
private Date createdDate;
#AttributeProperty
private Date expiryDate;
#AttributeProperty
private Partition partition;
// getter and setter omitted
}
And created a new entity to map to this model:
public class IdentityTypeEntity implements Serializable {
#Id
#Identifier
private String id;
#OneToOne(optional = false, mappedBy = "identityType",
cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#AttributeValue
// #NotNull
private HAccount accountEntity;
#IdentityClass
private String typeName;
#ManyToOne
#OwnerReference
private PartitionEntity partition;
#AttributeValue
private String username;
#AttributeValue
// #Transient
private boolean enabled;
#AttributeValue
private Date createdDate;
#AttributeValue
private Date expiryDate;
}
PL can map property with #AttributeProperty to entity property with #AttributeValue. But it can only map to one entity. Therefore there is no way to map, say User and its properties over to Account and Person. But you can have the entity (in my case accountEntity) in the model. I also have to duplicate a few fields in the new IdentityTypeEntity and my existing Account entity (username, eanbled, createdDate) because PL requires these. Use a #PrePersist and similar to sync them.

Avaje Ebean. ManyToMany deferred BeanSet

I am writing small app, using Play Framework 2.0 which uses Ebean as ORM.
So I need many-to-many relationship between User class and UserGroup class.
Here is some code:
#Entity
public class User extends Domain {
#Id
public Long id;
public String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public Set<UserGroup> groups = new HashSet();
}
#Entity
public class UserGroup extends Domain {
#Id
public Long id;
public String name;
#ManyToMany(mappedBy="groups")
public Set<User> users = new HashSet();
}
Database scheme generator generates good scheme for that code with intermediate table and all work quite ok, till I using many-to-many.
So I am adding group in one request:
user.groups.add(UserGroup.find.byId(groupId));
user.update();
And trying output them to System.out in another:
System.out.println(user.groups);
And this returns:
BeanSet deferred
Quick search show that BeanSet is lazy-loading container from Ebean. But seems like it doesn't work in proper way or I missed something important.
So is there any ideas about what I am doing wrong?
You need to save associations manually
user.groups.add(UserGroup.find.byId(groupId));
user.saveManyToManyAssociations("groups");
user.update();

Mutually referential fields in Play

I am trying to model users with home directories in my system. I got the following model declarations:
#Entity
public class Directory extends Model {
public String name;
#ManyToOne public Directory parent;
#ManyToOne public User owner;
#OneToMany public Set<User> sharees;
}
#Entity
public class User extends Model {
#Unique #Column(unique=true) public String username;
public String password;
public Directory homeDirectory;
public User(String username, String password) {
this.username = username;
this.password = password;
this.homeDirectory = new Directory(username, null, this);
}
}
When I create a user and call .save(), I get an error (A javax.persistence.PersistenceException has been caught, org.hibernate.exception.GenericJDBCException: could not insert: [models.User]). Can anyone explain why?
Using fixtures, can I test this? I'd need to create forward references in my yaml file, but I'm not sure if that's possible.
Thanks,
Vincent.
The error is thrown because of a missing #OneToOne annotation for homeDirectory.
I assume you're creating a directory for each user. If that's the case, then you should also use CascadeType.ALL so these directories automatically get created/deleted when users get created/deleted.
And no Yaml does not support forward references,
so you'll have to work around that when using bidirectional relations.