How do I add an interceptor to save/fetch methods of Spring's JPARepository to update transient values? - spring

First let me say I'm a complete novice with Spring AOP, and I apologize if this is a duplicate question.
Here's my scenario:
Let's say I have the following domain class:
#Entity(name="MyTable")
#Table(name="MY_TABLE")
public class MyTable {
private static final long serialVersionUID = 1234567890123456L;
#Id
#Column(name = "USER_ID")
private Long userID;
#Transient
private String key;
#Column(name = "KEY")
private String secureKey;
/* Other columns */
/* Getters and Setters */
}
and I have the following JPARepository class to manage it:
#Repository
public interface MyTableRepository extends JpaRepository<MyTable, Long> {
/* findBy methods */
}
As you can see, I have a secureKey field and a transient key field. In this case secureKey is an encrypted version of key.
What I need is for the secureKey value to be populated before a domain object is saved, and for the key value to be populated after a domain object is fetched. (This is a trivial example but in the real case I have multiple transient and encrypted values.) The idea is for the secure values to be persisted to the DB, but users of the domain class will only need to work with the "insecure" values.
Currently I'm handling this in my service layer. After I call a fetch method I'm populating the transient values, and before calling a save method I'm populating the "secure" values. This is working as expected but ideally I'd like this to be managed transparently, because now the burden is on each developer to remember to update those values after fetching or before saving.
I'm assuming the best way to handle this would be through some AOP class, but I confess I have little to no idea where to begin there. Is this a common scenario, and if so, would someone be willing to point me in the right direction? Also, if you have a suggestion for a better way to implement this decrypted/encrypted field pair scenario, please let me know.
Ideally I'd like to be able to add an annotation to both the secure and insecure fields, maybe pointing to each other, maybe something like:
#Insecure(secureValue = "secureKey")
#Transient
private String key;
#Secure(insecureValue = "key")
#Column(name = "KEY")
private String secureKey;
Any assistance you could provide is most appreciated.
Thanks,
B.J.

I think Spring AOP isn't the correct technology in your use case, i would recommend to use EntityListeners.
Hibernate: https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/listeners.html
Eclipselink: https://wiki.eclipse.org/EclipseLink/Release/2.5/JPA21#CDI_Entity_Listeners

Related

How to access Spring properties from an entity?

I have a spring app, that pushes data in an s3 bucket.
public class Ebook implements Serializable {
#Column(name= "cover_path", unique = true, nullable = true)
private String coverPath;
private String coverDownloadUrl;
#Value("${aws.cloudfront.region}")
private String awsCloudFrontDns;
#PostLoad
public void init(){
// I want to access the property here
System.out.println("PostConstruct");
String coverDownloadUrl = "https://"+awsCloudFrontDns+"/"+coverPath;
}
When a data is pushed, let's say my cover here, I get the key 1/test-folder/mycover.jpg which is the important part of the future http URL of the data.
When I read the data from database, I enter inside #PostLoad method and I want construct the complete URL using the cloudfront value. This value changes frequently so we don't want to save hardly in the database.
How could I do to construct my full path just after reading the data in database?
The only way to do this is to use a service that update the data after using repository to read it? For readbyId it can be a good solution, but for reading list or using other jpa methods, this solutions won't work because I have each time to create a dedicated service for the update.
It doesn't look good for Entity to depend on property.
How about EntityListener.
#Component
public class EbookEntityListener {
#Value("${aws.cloudfront.region}")
private String awsCloudFrontDns;
#PostLoad
void postload(Ebook entity) { entity.updateDns(awsCloudFrontDns); }
}
I recommend trying this way :)

Is there functionality in JPA/Spring/Hibernate/etc similar to JPA's #PrePersist that would allow changes to related entities?

I need to store normalized (i.e. without special characters etc.) variants of some of the String fields of some entities.
An example:
#Entity
public class Car {
#Id
private Long id;
private String make;
private String model;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "CAR_ID")
private Set<NormalizedField> normalizedFields = new HashSet();
private Set<NormalizedField> createNormalizedFields(Car car) {
Set<NormalizedField> normalized = normalize(car);
this.normalizedFields.clear();
this.normalizedFields.addAll(normalized);
}
// I would use this approach, but it doesn't allow
// changes to related entities.
// #PreCreate
// public void onCreate() {
// createNormalizedFields();
// }
}
#Entity
public class NormalizedField {
#Id
private Long id;
private String fieldName;
private String normalizedValue;
}
It would be convenient if the normalized values were automatically (re)created whenever the Car entity is persisted. Is there a way to trigger the creation method automatically?
Using #PrePersist, #PreUpdate... is obviously not an option as it doesn't allow changes to related entities.
Spring AOP is not used in the project, so I would rather avoid introducing it for now. But it's an option anyways.
The application is huge, and managing the normalized values 'manually' would require quite a bit of work, hence I leave it as the last option.
Going to post this half-answer here ('half' because it provides a workaround with restrictions).
In some cases org.hibernate.Interceptor can be used to manage child entities whenever the parent entity is changed.
But there are restrictions: the javadoc says Session is not to be used in the Interceptor. JPA repository methods, JPQL or HQL calls are intercepted by the same Interceptor in a loop. Even native queries get intercepted unless you set FlushMode.COMMIT or FlushMode.MANUAL (and maybe some other).
The above means you'll probably have to use the datasource directly. I don't remember exactly how, but Spring provides means to execute queries using datasource directly and within current transaction. In my case it was enough as I had to manage some technical child entities that didn't need a representation as an Entity.

Spring data MongoDB adding arrays to an existing document

Say I have the following Collections
public #Data class Customer {
#Id
private String id;
private String firstName;
private String lastName;
#DBRef
private List<Address> addressList= new ArrayList<Address>();
}
and
public #Data class Address {
#Id
private String id;
private String address;
private String type;
private String customerID;
}
And each Customer has multiple addresses, and I have implemented MongoRepository. Saving customer for the First time is working pretty well customerRepo.save(customerObject) and before calling the save I am persisting multiple Address Objects and then setting those to the addressList.
Next time when I am updating the same document and want to add a New set of Address to the existing list it is overwriting the whole addressList array. So basically what I have to do now to set new address like thisexistingCustomerObject.getAddressList().addAll(my new List of address) if there are thousand(or more than thousand) of elements or I am slicing the addressList array the following procedure won't be a good idea. My question is what is the best way to achieve this scenario? say if I don't want to use MongoTemplate. Is it possible Just using the MongoRepository
I don't think you can do it in that way. Previously i had the same situation, and I tried the following
1.org.springframework.core.convert.converter.Converter even I have managed to manipulate the DBObject but functions like $push or $set(wrapping them under key) does not work over there.
2.AbstractMongoEventListener by overriding onBeforeSave but Object manipulation was not taking place during save.
However you can try altering the mentioned
you can try override MongoRepository save method, It would better if someone point to the right direction.
Otherwise for my scenario I had to create Custom repository(To update and delete document) which is working parallel with MongoRepository (for Insert and retrieve data/document), but I believe thats an ugly workaround. There has to be a cleaner way to do it.

Entity Objects vs Value Objects - Hibernate and Spring

Okay. I am getting a little confused here...
Lets say I have a class called User.
class User {
// all variables
// all getters and setters
}
Now, I use JSR 303 validation and put #NotNull, #Range, etc here for the variables.
I use this as Form / Command object. Meaning, when a form a submitted, the values are validated and BindingResult gives me errors.
Should this be used as in Entity Object for Hibernate as well? (If so, I need to add other Hibernate annotations like #Entity, #Id, #Column, etc on top of Validation annotations)
When we load the data from Database, do these validations kick in as well? (If yes, what if the data is already existing, and do not confirm to the validations?)
Where do we normally write business validations, like for example, country exists or not in the database, xyz value exists in a different table, etc?
Questions arise here as well:
- User form may not have all the fields that exist in the User class
- Database table User may have more fields or less fields than User class
- Form may have fields from different objects as well, say User and and Order.
How do we handle these?
Trying to wrap my mind around it
No you shouldn't mix entities objects and values objects. Entities objects are for DB mapping and values objects are used in the presentation layer.
To validate an object annoted, you need to use a Validator (commonly used with a DataBinder. Spring validation)
Is it DB constraints or backend validation?
For your last question, that's one of the reason to have 2 differentes objects for your presentation layer and your persistence layer. This way values objects can match what is displayed or input by the user (Form).
Entity object is an object of our plain old java class(POJO) Model/Business class, which needs to be persisted in a database using Hibernate, while Value Type object is an object of another class but it is stored as a part of Entity object within a database table.
#Embeddable
#Data
#AllArgsConstructor
public class Address{
private String country;
private String city;
private String street1;
private String street2;
private postalCode;
}
#Entity
#Data
#AllArgsConstructor
public class Person{
#Id
private Long id;
private String firstName;
private String lastName;
private ing age;
private Address address;
}
}
So after running this code in spring and hibernate project you will see Person table in database is created with it's attributes and Address class attributes.
For more information, I suggest reading this:
[https://www.decodejava.com/hibernate-value-type-object.htm][1]

Multiple Relationship classes with the same type

Using spring-data-neo4j, I want to create two classes using #RelationshipEntity(type="OWNS") to link a Person class to both a Pet and Car.
#RelationshipEntity(type="OWNS")
public class OwnsCar {
#Indexed
private String name;
#StartNode
private Person person;
#EndNode
private Car car;
}
#RelationshipEntity(type="OWNS")
public class OwnsPet {
#Indexed
private String name;
#EndNode
private Person person;
#StartNode
private Pet pet;
}
This saves to the Graph Database properly with no problems, as I can query the actual Node and Relationship and see they type, etc.
But when I attempt to use #RelatedTo(type="OWNS", elementClass=Pet.class) I either get a class cast exception, or when using lazy-initialization I get incorrect results.
#NodeEntity
public class Person {
#Indexed
private String name;
#RelatedTo(type="OWNS", direction=Direction.OUTGOING, elementClass=Pet.class)
private Set<Pet> pets;
#RelatedTo(type="OWNS", direction=Direction.OUTGOING, elementClass=Car.class)
private Set<Car> cars;
}
The result I get when I attempt to print our my person(my toString() has been omitted, but it simply calls the toString() for each field) is this:
Person [nodeId=1, name=Nick, pets=[Car [nodeId=3, name=Thunderbird]], cars=[Car [nodeId=3, name=Thunderbird]]]
Does anyone know if this can be done, should be done, is just a bug or a feature that is missing?
It seems like the problem is, that the annotation causes springDataNeo4j to priorize the relationship name. I tried the same on another sample I created. If both annotations contain
type="OWNS" it mixes both 'objects'. When I omit this information, and only use direction and type, it works for me.
Unfortunately this will lead to a problem if you are using another #RelatedTo annotation with more Pets or Cars related with another annotation. As it would not differ between "OWNS" and any other relation to a Pet-Type, the set returns all related pets (example: peter ->(HATES-Relationsip)->dogs).
If it's a bug or not, I can't tell... But for the database: There are only nodes and relations. Both are not typed, so neo4j does not know anything about your 'Pet'- or 'Car'-Class. Spring data neo4j handles this, either by indexing all nodes per type and setting a type-attribute, or using a specific graph-layout (with subreferences). Even if you would want to fetch all pets of a person with a traversal description, you would have so much more code to write, since the outgoing relations with name 'OWNS' contains two types of objects.
I would recommend using two different names. It's easier to write your custom traversals/queries later on, and its probably even faster as well, because no class-type comparison will be needed. Is there any reason, why you would need these specific names?
PS: It is possible, that not everything is 100% accurate. I don't know springdataneo4j in detail, but that's what I figured out so far.

Resources