I have a scenario where I have 2 labels that need to be configured. The names of the labels are 'Out Date' and 'In Date'. I only have one field in the database called 'Date'. Whether it is 'Out' or 'In' is decided at the runtime by the value of an Enum 'Scenario'. However, I need to actually show the user Out Date&In Date so that he can select 1 or both of them. I heard that calculated field concept it JPA will assist in this. Is this true or is there some other way that I can achieve this. Below is some sample code.
Date
#Override
#Convert("DateTimeConverter")
#Column(name = "DATE")
public DateTime getDate() {
return date;
}
Scenario
#Override
#Convert("EnumConverter")
#Column(name = "SCENARIO")
public Scenario getScenario() {
return scenario;
}
Scenario is any enum with the values OUT(1),IN(2)
There are no calculated properties in JPA.
You can use #Transient annotation to create properties that are not persisted but calculated based on other fields:
#Transient
public DateTime getInDate() {
if (scenario == Scenario.IN) {
return date;
}
return null;
}
#Transient
public DateTime getOutDate() {
if (scenario == Scenario.OUT) {
return date;
}
return null;
}
Alternatively, if you are using Hibernate you can use proprietary annotation #Formula:
#Formula("case when SCENARIO = 2 then DATE else NULL end")
#Convert("DateTimeConverter")
private DateTime inDate;
#Formula("case when SCENARIO = 1 then DATE else NULL end")
#Convert("DateTimeConverter")
private DateTime outDate;
I prefer the first option because:
it is easier to test with unit tests
it is easier to use the entity in unit tests
it does not require proprietary extensions
generally there might be some problems with portability of SQL, although in this problem case when is SQL 92 compatible so it does not apply here
The only problem I can is is that in simplest approach is that we abandon encapsulation by exposing to clients internals of the entity (scenario and date properties). But you can always hide these properties with accessor protected, JPA will still handle that.
To compute properties within JPA entities, you can use JPA callbacks.
See this Hibernate JPA Callbacks documentation. (Note: JPA callbacks are not specific to hibernate, it's part of latest JPA 2.1 specification).
And also this OpenJpa JPA Calbacks one.
Following entity life-cycle categories have a Pre and Post event which can be intercepted by the entity manager to invoke methods:
Persist -> #PrePersist, #PostPersist
Remove -> #PreRemove, #PostRemove
Update -> #PreUpdate, #PostUpdate
Load -> #PostLoad (No Pre for this ...)
So let's say you want to compute a complexLabel label from two persisted entity fields label1 and label2 in an entity titled MyEntity:
#Entity
public class MyEntity {
private String label1;
private String label2;
#Transient
private String complexLabel;
#PostLoad
#PostUpdate // See EDIT
// ...
public void computeComplexLabel(){
complexLabel = label1 + "::" + label2;
}
}
As #Dawid wrote, you have to annotate complexLabel with #Transient in order to make them ignored by persistence. If you don't do this, persistence fails because there is no such column in MyEntity corresponding table.
With #PostLoad annotation, computeComplexLabel() method is called by entity manager just after the loading of any instance of MyEntity from persistence.
Thus, #PostLoad annotated method is best suited to put your post loading entity properties enhancement code.
Bellow is an extract from JPA 2.1 specification about PostLoad:
The PostLoad method for an entity is invoked after the entity has been
loaded into the current persistence context from the database or
after the refresh operation has been applied to it. The PostLoad
method is invoked before a query result is returned or accessed or
before an association is traversed.
EDIT
As pointed out by #Dawid, you could also use #PostUpdate in case you want to compute this transient field just after the entity update, and use other callbacks when needed.
Related
I have a simple entity with a requirement that last modified time should be updated on persist.
#Data // Lombok thing
#Entity
#Table(name = "MY_ENTITY")
public class MyEntity {
#Column(name = "LAST_MODIFIED", nullable = false)
private LocalDateTime lastModified;
// irrelevant columns including id omitted
#PrePersist
public void initializeUUID() {
lastModified = LocalDateTime.now();
}
}
I have a requirement to implement a job that queries such entities older than a certain time (let's say a day), modifies its state and persists them. I have a problem with data creation for an unit test that covers such use case.
Although I set manually lastModified time, the #PrePersist causes its change regardless the set value.
#Autowired // Spring Boot tests are configured against in-memory H2 database
MyEntityRepository myEntityRepository;
var entity = new MyEntity();
entity.setLastModified(LocalDateTime.now().minusDays(3));
myEntityRepository.entity(entity);
Question: How to prepare pre-persisted data (lastModified) without drastically modifying the MyEntity class just for sake of unit tests? A solution using Mockito is welcome.
Note I use Spring Boot + jUnit 5 + Mockito
Things I have tried:
How to mock persisting and Entity with Mockito and jUnit: Mocking persisting the entity is not a way to go because I need the entity to be persisted in H2 for further checks. Moreover, I tried to use spy bean using this trick Spring Boot #7033 with the same result.
Hibernate Tips: How to activate an entity listener for all entities: Adding listener programatically using static nested class configured #TestConfiguration for the unit test scope. The thing is not called at all.
#TestConfiguration
public static class UnitTestConfiguration { // logged as registered
#Component
public static class MyEntityListener implements PreInsertEventListener {
#Override
public boolean onPreInsert(PreInsertEvent event) { // not called at all
Object entity = event.getEntity();
log.info("HERE {}" + entity); // no log appears
// intention to modify the `lastModified` value
return true;
}
}
Dirty way: Create a method-level class extending MyEntity with #PrePersist that "overrides" the lastModified value. It results in org.springframework.dao.InvalidDataAccessApiUsageException. To fix it, such entity relies on the #Inheritance annotation (JPA : Entity extend with entity), which I don't want to use just for sake of unit tests. The entity must not be extended in the production code.
You could use the Spring Data JPA AuditingEntityListener.
Simply enable it via #org.springframework.data.jpa.repository.config.EnableJpaAuditing and optionally provide a custom dateTimeProviderRef like this:
#Configuration
#EnableJpaAuditing(dateTimeProviderRef = "myAuditingDateTimeProvider")
public class JpaAuditingConfig {
#Bean(name = "myAuditingDateTimeProvider")
public DateTimeProvider dateTimeProvider(Clock clock) {
return () -> Optional.of(now(clock));
}
}
Your entity could look something like this then:
#Data // Lombok thing
#Entity
#Table(name = "MY_ENTITY")
#EntityListeners(AuditingEntityListener.class)
public class MyEntity {
#LastModifiedDate
private LocalDateTime lastModified;
// irrelevant columns including id omitted
}
In the above example a java.time.Clock can be provided via Spring which could already solve your question regarding testing. But you could also provide a dedicated test config specifying a different/mocked DateTimeProvider.
Please note that the mentioned solution here is not a pure unit test approach. But based on your question and the things you've tried, I concluded that a solution using Spring would be feasible.
With Mockito you could perhaps do something like this?
MyEntity sut = Mockito.spy(new MyEntity());
Mockito.doNothing().when(sut).initializeUUID();
But I'm not sure exactly where it would fit in your tests.
Two other options
Mock LocalDateTime.now() and let it return the value you want. But maybe other code in the persist process calls this method and may not like it. If that is the case, over to the other option
Wrap LocalDateTime.now() in your own class with a static method and mock that instead. Sadly involves minor changes to your entity class, but only that call to LocalDateTime.now() will be mocked.
I haven't described how to mock with Mockito in this case because I'm not familiar with it. I've only used JMockit. But the above would be the principle.
I'm updating an existing code that handles the copy or raw data from one table into multiple objects within the same database.
Previously, every kind of object had a generated PK using a sequence for each table.
Something like that :
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
In order to reuse existing IDs from the import table, we removed GeneratedValue for some entities, like that :
#Id
#Column(name = "id")
private Integer id;
For this entity, I did not change my JpaRepository, looking like this :
public interface EntityRepository extends JpaRepository<Entity, Integer> {
<S extends Entity> S save(S entity);
}
Now I'm struggling to understand the following behaviour, within a spring transaction (#Transactional) with the default propagation and isolation level :
With the #GeneratedValue on the entity, when I call entityRepository.save(entity) I can see with Hibernate show sql activated that an insert request is fired (however seems to be only in the cache since the database does not change)
Without the #GeneratedValue on the entity, only a select request is fired (no insert attempt)
This is a big issue when my Entity (without generated value) is mapped to MyOtherEntity (with generated value) in a one or many relationship.
I thus have the following error :
ERROR: insert or update on table "t_other_entity" violates foreign key constraint "other_entity_entity"
Détail : Key (entity_id)=(110) is not present in table "t_entity"
Seems legit since the insert has not been sent for Entity, but why ? Again, if I change the ID of the Entity and use #GeneratedValue I don't get any error.
I'm using Spring Boot 1.5.12, Java 8 and PostgreSQL 9
You're basically switching from automatically assigned identifiers to manually defined ones which has a couple of consequences both on the JPA and Spring Data level.
Database operation timing
On the plain JPA level, the persistence provider doesn't necessarily need to immediately execute a single insert as it doesn't have to obtain an identifier value. That's why it usually delays the execution of the statement until it needs to flush, which is on either an explicit call to EntityManager.flush(), a query execution as that requires the data in the database to be up to date to deliver correct results or transaction commit.
Spring Data JPA repositories automatically use default transactions on the call to save(…). However, if you're calling repositories within a method annotated with #Transactional in turn, the databse interaction might not occur until that method is left.
EntityManager.persist(…) VS. ….merge(…)
JPA requires the EntityManager client code to differentiate between persisting a completely new entity or applying changes to an existing one. Spring Data repositories w ant to free the client code from having to deal with this distinction as business code shouldn't be overloaded with that implementation detail. That means, Spring Data will somehow have to differentiate new entities from existing ones itself. The various strategies are described in the reference documentation.
In case of manually identifiers the default of inspecting the identifier property for null values will not work as the property will never be null by definition. A standard pattern is to tweak the entities to implement Persistable and keep a transient is-new-flag around and use entity callback annotations to flip the flag.
#MappedSuperclass
public abstract class AbstractEntity<ID extends SalespointIdentifier> implements Persistable<ID> {
private #Transient boolean isNew = true;
#Override
public boolean isNew() {
return isNew;
}
#PrePersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
// More code…
}
isNew is declared transient so that it doesn't get persisted. The type implements Persistable so that the Spring Data JPA implementation of the repository's save(…) method will use that. The code above results in entities created from user code using new having the flag set to true, but any kind of database interaction (saving or loading) turning the entity into a existing one, so that save(…) will trigger EntityManager.persist(…) initially but ….merge(…) for all subsequent operations.
I took the chance to create DATAJPA-1600 and added a summary of this description to the reference docs.
I have an entity which belongs to a customer entity.
I would like the entity including all associations to be kind of read-only.
public class Foo{
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "customer_id")
private Customer customer;
#Basic
#Column(name = "firstname")
private String firstName;
// getters for both fields ....
}
Here is what I want:
Calls to setters of foo should not be persisted.
Calls to myFoo.getCustomer() should return a readonly customer so that calls to setters like myFoo.getCustomer().setSomething("should not be persisted") should not work.
Example:
List<Foo> list = fooDAO.getList();
for (Foo foo : liust) {
String f1 = foo.getCustomer().getSomeField(); // should work
foo.getCustomer.setSomeField("change that should not be persisted"); // calling this setter should not have an effect or should even throw an UnsupportedOperationException() or something
foo.setFirstName("change should not be persisted"); // also this should not be persisted.
}
Currently my solution for the association is kind of manual:
public Customers getCustomer() {
// detach the referenced object from the Hibernate session
// to avoid that changes to these association are persisted to the database
getCustomersDAO().evict(customer); // calls session.evict(o) under the hood
return customer;
}
Here is my question:
What ways are there to avoid changes to associations being persisted to the Database? E.g. using an annotation?
In general I would like this behaviour to be the default.
But it should also be possible to allow changes to be persisted. So I need it configurable. So I though about doing it on the Query-level.
My environment:
Hibernate v3.6
Spring 3.2 with HibernateDAOSupport /
HibernateTemplate and annotation based Transaction-handling.
I ran into this issue. I was storing filename as a property of an object in the database, and then trying to manipulate it in the application after reading it to reflect storage path based on environment. I tried making the DAO transaction readOnly, and hibernate still persisted changes of the property to the db. I then used entityManager.detach(object) before I manipulated the property, and that worked. But, I then later got tangled up into issues where other service methods that were dependent the auto persistence of objects to the db weren't behaving as they should.
The lesson I learned is that if you need to manipulate a property of a object that you don't want persisted, then make a "transient" property for that object with it's own getters and setters, and make sure the transient property setter isn't passed properties from the entity model that then become manipulated. For example, if you have a property "filename" of String, and you have a transient property of "s3key", the first thing the "s3Key" property setter should do is make a new String object passed into the setter for it to use in the manipulation.
#Transient
private String s3Key;
public String getS3Key {
String s3key = s3Prefix + "/" + <other path info> + this.filename;
return s3key;
}
Though all the "readOnly" and "detach" stuff for jpa/hibernate allows for custom handling of persistence of objects to the db, it just seems going to that length to customize the behavior of the objects to that extent causes supportability problems later, when persisting entity changes automatically is expected or counted on in other service functions. My own experience has been it is best to use a programming pattern of making use of transient properties for handling changes to entities you don't want persisted.
I'm using Camel and JPA to persist entities to a Postgres DB. In each entity I have a field called "history" which contains all the old values of the given entity. I'm looking for a way to populate this field automatically before each update operations.
Surfing the web, I've found the JPA interceptors, but I've seen that they are used for auditing/logging purposes. Am I wrong?
What's the best way to do this?
JPA/Hibernate interceptors (which one depends on the version you're using) are one way to do this. Auditing/logging are similar to what you want to do, i.e. automatically update some column/property when the entity itself is updated (any property). Just note that manual update queries circumvent those interceptors so those should be avoided.
How you use those interceptors depends on how you want to implement that history functionality though. If you're doing it by generating some string/byte representation and storing it in a column it should work. If you're planning to create another entity etc. you might have to collect the changes/old values in the interceptor and upon successful commit you store the collected values. AFAIK it's not possible (at least not easy) to create a new entity when the interceptors have been invoked.
#Entity
#Table(name = "entities")
public class Entity {
...
private Date created;
private Date updated;
#PrePersist
protected void onCreate() {
created = new Date();
}
#PreUpdate
protected void onUpdate() {
updated = new Date();
}
}
You can use #EntityListeners and provide your entity Listener class to it, and you can also reuse this whenever you want
In your entity Listener class, you can provide callback methods with #PrePersit, #PostPersist, #PreUpdate, #PostUpdate, #PreDelete, #PostDelete annotations. These methods will get called automatically for their respective actions.
You can read Spring Data JPA Auditing: Saving CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate automatically for more details.
I have the following model
#Entity
class Element {
#Id
int id;
#Version
int version;
#ManyToOne
Type type;
}
#Entity
class Type {
#Id
int id;
#Version
int version;
#OneToMany(mappedBy="type")
Collection<Element> elements;
#Basic(optional=false)
boolean disabled;
}
and would like to allow Type.disabled = true only if Type.elements is empty. Is there a way to do it atomically?
I would like to prevent an insertion of an Element in a transaction while the corresponding Type is being disabled by an other transaction.
Update: sorry I didn't make myself clear. I'm not asking how to trigger the check, but how to prevent a sequence like this:
Transaction 1 checks that Type.elements is empty
Transaction 2 checks that Type.disabled = false
Transaction 1 updates Type and sets disabled = true
Transaction 2 persists a new Element
Transaction 2 commits
Transaction 1 commits
I then have a situation where Type.elements is not empty and Type.disabled = true (broken invariant). How can I avoid this situation? In native SQL, I would use pessimistic locking, but JPA 1.0 does not support this. Can the problem be solved using optimistic locking?
I would like to prevent an insertion of an Element in a transaction while the corresponding Type is being disabled by an other transaction.
I would use the Bean Validation API (JSR 303) and a custom constraint for this purpose. If you are not familiar with Bean Validation, I suggest reading the following entries:
Bean Validation Sneak Peek part I
Bean Validation Sneak Peek part II: custom constraints
Bean Validation Sneak Peek part III: groups and partial validation
while Bean Validation as suggested by Pascal is more elegant, a quick and easy solution would be entity lifecycle listener methods inside the entity class:
#PrePersist #PreUpdate
protected void validate(){
if(this.disabled && !this.elements.isEmpty()){
throw new IllegalArgumentException(
"Only types without elements may be disabled");
}
}
or better yet, have an Assert class (as in JUnit or Spring that encapsulates the exception throwing):
#PrePersist #PreUpdate
protected void validate(){
Assert.same( // but this implies a bi-directional constraint
this.disabled, this.elements.isEmpty(),
"Only types without elements may be disabled and vice-versa"
);
}