How can I create an entity in a transaction with unique properties? - java

I am using objectify. Say, I have a User kind with name and email properties. When implementing signup, I want to check if a user with same name or same email is already registered. Because signup can be called from many sources a race condition might occur.
To prevent race condition everything must be wrapped inside a transaction somehow. How can I eliminate the race condition?
The GAE documents explain how to create an entity if it doesn't exist but they assume the id is known. Since, I need to check two properties I can't specify an id.

Inspired by #konqi's answer I have came up with a similar solution.
The idea is to create User_Name and User_Email entities that will keep the name and emails of all the users created so far. There will be no parent relationship. For convenience we are going to keep name and email properties on user too; we are trading storage for less read/write.
#Entity
public class User {
#Id public Long id;
#Index public String name;
#Index public String email;
// other properties...
}
#Entity
public class User_Name {
private User_Name() {
}
public User_Name(String name) {
this.name = name;
}
#Id public String name;
}
#Entity
public class User_Email {
private User_Email() {
}
public User_Email(String email) {
this.email = email;
}
#Id public String email;
}
Now create user within a transaction by checking unique fields:
User user = ofy().transact(new Work<User>() {
#Override
public User run()
{
User_Name name = ofy().load().key(Key.create(User_Name.class, data.username)).now();
if (name != null)
return null;
User_Email email = ofy().load().key(Key.create(User_Email.class, data.email)).now();
if (email != null)
return null;
name = new User_Name(data.username);
email = new User_Email(data.email);
ofy().save().entity(name).now();
ofy().save().entity(email).now();
// only if email and name is unique create the user
User user = new User();
user.name = data.username;
user.email = data.email;
// fill other properties...
ofy().save().entity(user).now();
return user;
}
});
This will guarantee uniqueness of those properties (at least my tests empirically proved it :)). And by not using Ref<?>s we are keeping the data compact which will result in less queries.
If there was only one unique property it is better to make it #Id of the main entity.
It is also possible to set the #Id of the user as email or name, and decrease the number of new kinds by one. But I think creating a new entity kind for each unique property makes the intent (and code) more clear.

I can think of two possible solutions:
Using entity design:
Lets say you have a User #Entity which will use the email address as #Id. Then you create a Login #Entity which has the Name as #Id and a Ref<User> to the User. Now both can be queried for with key queries which can be used in transactions. This way it's impossible to have duplicates.
#Entity
public class User {
#Id
private String email;
}
#Entity
public class Login {
#Id
private String name;
private Ref<User> user;
}
Using indexed composite property:
You can define an indexed composite property that contains both values like this (Note: This just shows what i mean by indexed composite property, do not implement it like that):
#Entity
public class User {
#Id
private Long id;
private String email;
private String name;
#Index
private String composite;
#OnSave
private onSave(){
composite = email + name;
}
}
However, as stickfigure pointed out there is no guarantee for uniqueness if you use an indexed property in a transaction (as a matter of fact you can't query by indexed property in a transaction at all).
That is because in a transaction you can only query by key or ancestor. So what you need to to is outsource your composite key into a separate #Entity that uses the composite key as #Id.
#Entity
public class UserUX {
// for email OR name: email + name (concatenation of two values)
// for email AND name: email OR name
// (you would create two entities for each user, one with name and one with the email)
#Id
private String composite;
private Ref<User> user;
}
This entity is usable in a key query and therefor in a transaction.
Edit:
If, as commented on this answer you wish to 'restricts users with same email and name' you can use the UserUX entity as well. You would create one with the email and one with the name. I added code comments above.

This is from the python sdk but the concepts should translate to java
http://webapp-improved.appspot.com/_modules/webapp2_extras/appengine/auth/models.html#Unique
"""A model to store unique values.
The only purpose of this model is to "reserve" values that must be unique
within a given scope, as a workaround because datastore doesn't support
the concept of uniqueness for entity properties.
For example, suppose we have a model `User` with three properties that
must be unique across a given group: `username`, `auth_id` and `email`::
class User(model.Model):
username = model.StringProperty(required=True)
auth_id = model.StringProperty(required=True)
email = model.StringProperty(required=True)
To ensure property uniqueness when creating a new `User`, we first create
`Unique` records for those properties, and if everything goes well we can
save the new `User` record::
#classmethod
def create_user(cls, username, auth_id, email):
# Assemble the unique values for a given class and attribute scope.
uniques = [
'User.username.%s' % username,
'User.auth_id.%s' % auth_id,
'User.email.%s' % email,
]
# Create the unique username, auth_id and email.
success, existing = Unique.create_multi(uniques)
if success:
# The unique values were created, so we can save the user.
user = User(username=username, auth_id=auth_id, email=email)
user.put()
return user
else:
# At least one of the values is not unique.
# Make a list of the property names that failed.
props = [name.split('.', 2)[1] for name in uniques]
raise ValueError('Properties %r are not unique.' % props)
"""

Related

Java JPA enum-like String options

I am trying to implement a simple User-Roles relationship in a Spring application, for security. The basic entities (some fields and annotations trimmed):
User
#Table(name="usr")
public class User implements Serializable {
#Id
private UUID id;
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name="user_roles", joinColumns=#JoinColumn(name="user_id", referencedColumnName="id"),
inverseJoinColumns=#JoinColumn(name="role_id", referencedColumnName="id"))
private Collection<Role> roles;
}
Role
public class Role implements Serializable {
#Id
private UUID id;
#ManyToMany(mappedBy="roles")
private Collection<User> users;
private String name;
}
So far, so good. However, I also have a class that defines a list of role-name values:
UserRoles
public class UserRole {
public static final String ADMIN = "admin";
public static final String USER = "user";
}
I want to constrain the values of the Role's name field to the values in UserRoles, effectively like an enum.
These role values will get used within Spring Security functions that require roles to be string values. As such, if I were to make UserRoles an enum, any database storage would be of ints – the ordinal definition position within UserRoles – which would force me to keep any potentially deprecated options, and also require a hacky conversion every time I need to convert the role to a string that can be passed around in a JWT, etc. (If I want to look at my database directly, it will also be far less informative.)
Is there some way to define Role's name field as limited to the static values in UserRoles? (Changing how or where these values are stored is entirely acceptable.)
You can define like this
public enum UserRoleEnum {
USER, ADMIN
}
And in entity
#Enumerated(EnumType.ORDINAL)
private UserRoleEnum role;

"Upgrade" entity from base class to child

I have 2 entities where one is derived from another
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
private String login;
#Column(name = "password_hash",length = 60)
private String password;
}
and
#Entity
#Table(name = "bidder")
public class Bidder extends User {
#Column(length = 100, unique = true)
private String email;
}
When Bidder object is persisted, in both user and bidder tables created a record which have the same id value.
Initially for each system user - we create User entity, but after some time User needs to be "upgraded" to Bidder.
Is there a way I can "upgrade" User to Bidder in some elegant way?
I have several ideas -
insert a record in Bidder table with the same id, that User has
initially create Bidder entity object, but the thing is not all
users can be Bidders
create BidderInfo entity class which will have
One-to-One relatioship to User
don't create Bidder child class, but
add all the needful fields from Bidder to User which will convert
User to Bidder initially, and which is spoils the semantics of User
all the above ideas don't look like perfect solution.
If you don't really want to have 2 tables you can use InheritanceType.SINGLE_TABLE and a #disciminatorValue.
With this way, to change the type of your user you just need to change the discriminator value.
You'll find an example of "single table strategy" to this page.
This way seems to be the easiest, but it may not the best.

Hibernate #SQLDelete sql not adding schema

I am trying to use the #SQLDelete annotation of Hibernate to make soft deletion. It works well when the DB schema is static, i.e: passing it in the SQL.
Unfortunately, it seems the SQL is passed as is to EntityPersisters (cf EntityClass's method CustomSQL createCustomSQL(AnnotationInstance customSqlAnnotation) so I can't find a way to pass the schema name dynamically like in Native SQL queries using {h-schema}
Did anyone find a good workaround for this issue (I am using Hibernate 4.3.5)?
Edit: Unless there is a real solution, I ended up modifying the code source of org.hibernate.persister.entity.AbstractEntityPersister by replacing the schema placeholder when setting the custom SQL queries in method doLateInit.
Edit2: I have created an issue for this behaviour in Hibernate JIRA. I will create a pull request later today and I wish the Hibernate Team will accept it
Soft deletes using Hibernate annotations.
As linked author stated below:
I am currently working on a Seam application that has a need for soft deletes in the database. To the right you can see a snippet of my database diagram which contains a CUSTOMER and APP_USER table. This is just a straight forward one to many relationship but the important thing to note though is the “DELETED” field in each table. This is the field that will be used to track the soft delete. If the field contains a ‘1’ the record has been deleted and if it contains a ‘0’ the record hasn’t been deleted.
Before ORMs like Hibernate I would have had to track and set this flag myself using SQL. It wouldn’t be super hard to do but who wants to write a bunch of boilerplate code just to keep track of whether or not a record has been deleted. This is where Hibernate and annotations comes to the rescue.
Below are the 2 Entity classes that were generated by Hibernate using seamgen. I have omitted parts of the code for clarity.
Customer.java
//Package name...
//Imports...
#Entity
#Table(name = "CUSTOMER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
#SQLDelete(sql="UPDATE customer SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
#Where(clause="deleted <> '1'")
public class Customer implements java.io.Serializable {
private long id;
private Billing billing;
private String name;
private String address;
private String zipCode;
private String city;
private String state;
private String notes;
private char enabled;
private char deleted;
private Set appUsers = new HashSet(0);
// Constructors...
// Getters and Setters...
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "customer")
// Filter added to retrieve only records that have not been soft deleted.
#Where(clause = "deleted <> '1'")
public Set getAppUsers() {
return this.appUsers;
}
public void setAppUsers(Set appUsers) {
this.appUsers = appUsers;
}
}
AppUser.java
//Package name...
//Imports...
#Entity
#Table(name = "APP_USER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
#SQLDelete(sql="UPDATE app_user SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
#Where(clause="deleted <> '1'")
public class AppUser implements java.io.Serializable {
private long id;
private Customer customer;
private AppRole appRole;
private char enabled;
private String username;
private String appPassword;
private Date expirationDate;
private String firstName;
private String lastName;
private String email;
private String phone;
private String fax;
private char deleted;
private Set persons = new HashSet(0);
// Constructors...
// Getters and Setters...
}
The following 2 steps is all that I had to do to implement the soft delete.
Added the #SQLDelete annotation which overrides the default
Hibernate delete for that entity.
Added the #Where annotation to filter the queries and only return
records that haven’t been soft deleted. Notice also that in the
CUSTOMER class I added an #Where to the appUsers collection. This is
needed to fetch only the appUsers for that Customer that have not
been soft deleted.
Viola! Now anytime you delete those entities it will set the “DELETED” field to ‘1’ and when you query those entities it will only return records that contain a ‘0’ in the “DELETED” field.
Hard to believe but that is all there is to implementing soft deletes using Hibernate annotations.
Note:
also note that instead of using the #Where(clause="deleted ‘1’") statements you can use hibernate filter (http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-filters) to globally filter-out all ‘deleted’ entities. I found that defining 2 entity managers (‘normal’ one that filter deleted items, and one that doesn’t, for the rare cases…) is usually quite convenient.
Using EntityPersister
You can create a DeleteEventListener such as:
public class SoftDeleteEventListener extends DefaultDeleteEventListener {
/**
*
*/
private static final long serialVersionUID = 1L;
#Override
public void onDelete(DeleteEvent event, Set arg1) throws HibernateException {
Object o = event.getObject();
if (o instanceof SoftDeletable) {
((SoftDeletable)o).setStatusId(1);
EntityPersister persister = event.getSession().getEntityPersister( event.getEntityName(), o);
EntityEntry entityEntry = event.getSession().getPersistenceContext().getEntry(o);
cascadeBeforeDelete(event.getSession(), persister, o, entityEntry, arg1);
cascadeAfterDelete(event.getSession(), persister, o, arg1);
} else {
super.onDelete(event, arg1);
}
}
}
hook it into your persistence.xml like this
<property name = "hibernate.ejb.event.delete" value = "org.something.SoftDeleteEventListener"/>
Also, don't forget to update your cascades in your annotations.
Resource Link:
Hibernate: Overwrite sql-delete with inheritace
Custom SQL for CRUD operations
Custom SQL for create, update and delete
Use like this
#SQLDelete(sql = "UPDATE {h-schema}LEAVE SET STATUS = 'DELETED' WHERE id = ?", check = ResultCheckStyle.COUNT)
I think there are 2 way
First is to add:
app.datasource.schema=<schema_name>
to your application.properties.
The second is to use the schema in annotation to your table model

Can an entity be updated instead of being inserted using spring-data-jpa when entity ID is not known without issuing a query?

We have a Spring Boot/Data-JPA (1.3.3.RELEASE) application using Hibernate implementation where a CSV file is read and inserted into a database table called FIRE_CSV_UPLOAD. For records that are already present we just update them.
We retrieve record ID by querying for unique key (a combination of three columns) but this approach is inefficient for thousands of record in CSV file.
My question is how to update record without querying the table for unique key? If I do not query for ID then the record will be inserted instead of update.
I know one way which is to retrieve all records and store their unique key and ID pairs in a Map. Any other suggestions are very much appreciated. The codes are as below,
Note: They are minimized for brevity.
#Entity
#Table(name = "FIRE_CSV_UPLOAD",
uniqueConstraints={#UniqueConstraint(columnNames = {"account_number" , "account_type", "bank_client_id"})})
public class FireCsv {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NotNull
#Column(name="account_number")
private String accountNumber;
#NotNull
#Column(name="account_type")
private String accountType;
#NotNull
#Column(name="bank_client_id")
private String bankClientIdNumber;
...
other fields/getters/setters
}
--
public interface FireCsvRepository extends JpaRepository<FireCsv, Long> {
#Query("select u from FireCsv u where u.accountNumber = :accountNumber and u.accountType = :accountType and u.bankClientIdNumber = :bankClientIdNumber ")
FireCsv findRecord(#Param("accountNumber") String accountNumber,
#Param("accountType") String accountType,
#Param("bankClientIdNumber") String bankClientIdNumber);
}
--
#Service
public class FireCsvServiceImpl implements FireCsvService {
other fields/methods
...
#Override
#Transactional
public FireCsv save(final FireCsv fireCsv) {
FireCsv existingFireCsv = fireCsvRepository.findRecord(fireCsv.getAccountNumber(), fireCsv.getAccountType(), fireCsv.getBankClientIdNumber());
// If record exist then mark as update, if not as insert
if (existingFireCsv != null) {
fireCsv.setId(existingFireCsv.getId());
fireCsv.setRecordStatus(CSVUploadRecordStatus.CSV_UPDATE.getStatus());
}
else {
fireCsv.setRecordStatus(CSVUploadRecordStatus.CSV_INSERT.getStatus());
}
fireCsv.setRecordStatusDate(new java.sql.Timestamp(new Date().getTime()));
return fireCsvRepository.save(fireCsv);
}
}
You have to read before deciding to make an update or insert, I dont think there is a way around it.
To make that faster you should add an index to your database
using the three columns "account_number", "account_type", "bank_client_id".
Alternatively you can try to use an composite id using #IdClass as shown in
tutorial-jpa-composite-primary-key
The JPA provider should than automatically create the index for it.

Spring Ldap ODM how to map attribute

I'm creating a directory with spring boot, and I add the library Spring Ldap.
I created my odm of User and it works, but I don't know how to map attribute from another entry.
My users are in ou=people,dc=mycompany,dc=com
The user belongs to an unit in ou=units,dc=mycompany,dc=com
The user have 0 or 1 manager in `ou=people,dc=mycompany.' same as users
The user have 0 or more subsidiaries no attribute for that, I have to find it with the help of manager attribute.
Here my code:
#Entry( objectClasses = { "person", "top" }, base = "ou=People" )
public final class User{
#Id private Name dn;
private String fullname;
private String mail;
etc...
}
I would like to add private User manager and private String unit and private List<User> subsidiaries but I dont know how to map/link to another Entry.
There is currently no support for relations in Spring LDAP ODM. I'm not sure it would be quite worth the effort (since LDAP is not really a relational database anyway), but it could possibly be something we could take a shot at if the demand for it is high.
Facing the same issue, I've used a transient attribute in my User class :
#Attribute(name = "manager", syntax = "1.3.6.1.4.1.1466.115.121.1.12")
private Name managerName;
#Transient
private User manager;
Of course, you have to deal with the manager attribute yourself. Add the following code in your UserService class :
public User findUser(String uid) {
User user = getRepo().findByUid(uid);
if(null != user.getManagerName()) {
User manager = getRepo().findOne(user.getManagerName());
user.setManager(manager);
}
return user;
}
You have to write something similar when saving your user object, in order to fill the managerName attribute with its new value if required.

Categories