Hibernate computed properties - java

I have the following Hibernate entity:
#Entity
#Table(name = "jobs")
public class Job {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "jobs_j_id_seq")
#SequenceGenerator(sequenceName = "jobs_j_id_seq", name = "jobs_j_id_seq", allocationSize = 1)
#Column(name = "j_id")
private Long id;
#Column(name = "j_description", length = 300, nullable = false)
private String description;
#Column(name = "j_category", length = 50, nullable = false)
#Enumerated(EnumType.STRING)
private JobCategory category;
#Column(name = "j_job_provided", length = 50, nullable = false)
private String jobProvided;
#ManyToOne(fetch = FetchType.EAGER, optional = false, cascade = CascadeType.ALL)
#JoinColumn(name = "j_provider_id")
private User provider;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "job")
private Set<Review> reviews;
#Transient
private Long averageRating;
.
.
.
}
What is the correct way of computing the value of the averageRating variable? I've read about #Formula, but I understand this only works the first time the entity is fetched. Meaning that if a new review is added to the Job instance, JPA will update the job but the #Formula will not run again, leading to my value not being recomputed.
Is there a way of having a dynamic #Formula, that will react to changes?
I can always iterate through the reviews and calculate the averageRating, but as we all know this is not the way to go.

If you want to denormalize the schema, you can add an actual column on the table and handle the update with SQL triggers. Then you just annotate the property with #Generated(GenerationTime.ALWAYS) and Hibernate will after every update refresh that property. This might work with #Formula as well, but I would advise against this. What is the point of having this average? IMO you should just always compute it on demand and think about storing it only if that becomes a performance issue.

Related

Avoid updating entities with JPA based on timestamp column

let's consider two JPA entities A and B :
#Entity
#Table(name = "A")
#SequenceGenerator(name = "seq_a", allocationSize = 50, initialValue = 1)
public class A {
#Id
#GeneratedValue(generator = "seq_a", strategy = GenerationType.SEQUENCE)
#Column(name = "ID", insertable = false, updatable = false, unique = true, nullable = false)
private Long id;
#Column(name = "CODE")
private String code;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<B> bSet = new HashSet<>();
#Column(name = "CREATED_TIME")
private LocalDateTime createdTime;
//getters + setters
}
#Entity
#Table(name = "B")
#SequenceGenerator(name = "seq_b", allocationSize = 50, initialValue = 1)
public class B {
#Id
#GeneratedValue(generator = "seq_b", strategy = GenerationType.SEQUENCE)
#Column(name = "ID", insertable = false, updatable = false, unique = true, nullable = false)
private Long id;
#Column(name = "SOMETHING")
private String something;
#ManyToOne(optional = false)
#JoinColumn(name = "A_ID", nullable = false, updatable = false)
private A a;
#Column(name = "CREATED_TIME")
private LocalDateTime createdTime;
//getters + setters
}
then consider RestController (springboot context) that have one GET method used for retrieving detail of entity A :
#GetMapping("/{id}")
public ResponseEntity<ADTO> getA(#PathVariable(name = "id", required = true) Long id) {
return aRepository.findById(id)
.map(a -> new ResponseEntity<>(mapper.mapToDomain(a), HttpStatus.OK))
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
method POST used for creating records of A:
#PostMapping
public ResponseEntity<ADTO> addA(#RequestBody #Valid ADTO aDTO) {
return new ResponseEntity<>(mapper.mapToDomain(a.save(mapper.mapToEntity(ADTO))), HttpStatus.CREATED);
}
and PUT method for updating :
#PutMapping
public ResponseEntity<ADTO> updateA(#RequestBody #Valid ADTO aDTO) {
A a = aRepository.findById(aDTO.getId()).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
ADTO aDTOfound = mapper.mapToDomain(a);
BeanUtils.copyProperties(aDTO, aDTOfound);
return new ResponseEntity<>(mapper.mapToDomain(aRepository.save(mapper.mapToEntity(aDTOfound), HttpStatus.OK)));
}
then let's say that, createdTime attribute is updated everytime the entity is persisted (including created - updating createdTime attribute is done under the hood ...).
Then let's consider scenario, where two users at the same time are retrieving detail of the same entity A (id 1). If user X update the detail from the retrieved content via PUT method, is there any way how to avoid user Y to update the same entity with old content (notice that the createdTime attribute is updated on record with id 1) ? I know that one possible solution, is to make sure that the retrieved createdTime and one from aDTO in update method is the same, but is there any "more" standard solution for this problem ? For example, how to avoid updating entity A (if it was updated previously with USER 1) but let update the childs in Bset which ones for example were not updated by user 1 ...
This is typical problem statement of Optimistic Locking
Optimistic locking is a mechanism that prevents an application from
being affected by the "lost update" phenomenon in a concurrent
environment while allowing some high degree of concurrency at the same
time.
I will solve this problem using #Version, add #Version field in your entity like below
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(name = "student_name")
private String studentName;
#Column(name = "roll_number")
private String rollNumber;
#Column(name = "version")
#Version
private Long version;
}
In above case When we create an entity for the first time default version value will be zero
On update, the field annotated with #Version will be incremented and subsequent update will same version will fail with OptimisticLockException
The #Version annotation in hibernate is used for Optimistic locking while performing update operation. For more details you can visit https://vladmihalcea.com/jpa-entity-version-property-hibernate/

Strategy to use the same UUID across multiple entities

I have an entity classes in JPA as below in which maId acts as a primary key and foreign key for many other tables.
#Table(name = "Table1")
public class Test implements Serializable {
#Id
#Column(name = "maId", updatable = false, nullable = false)
private String id;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="maId")
private MFieldData fieldData;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="maId")
private MPS mps;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="maId")
private MJob mJob;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="maId")
private List<MExtension> mlExtensions;
private Date createdDate;
private Date lastUpdatedDate;
}
Now,this is my another entity.
#Table(name = "table 2")
public class Test implements Serializable {
#Id
#Column(name = "maId", updatable = false, nullable = false)
private String maId;
private Integer cmd;
private String routeId;
}
By the time I receive a request this is API. I need to Insert the data across multiple tables.
How to implement a custom UUID (maId) generator and use it in #Id?
I need to use the same maId which is the unique id for this request across multiple entities while inserting into the DB. I am using a dirty way to do that. But is there any JPA way to solve this problem?
Any help would be highly appreciated!
The request has to pass the UUID (Called correlation-id) to track. https://docs.oracle.com/javase/7/docs/api/java/util/UUID.html can be used. If you generate in code it may go unknown to caller. The correlation-id is used to track the request data across systems it may travel due to event publish as well.

combine joined columns of a table with one column of another table into JAVA object

I want to combine column of different tables in one entity (object) but im getting a column null even it's not null.
I have two entities called Operation and CanceledOperation:
Operation:
#Entity
#Table(name = "OPERATIONS")
public class Operation{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#Column(name = "Date")
private Timestamp date;
#Transient
private String message;
// Other attributes and getters & setters
}
CanceledOperation:
#Entity
#Table(name = "CANCELED_OPERATIONS")
public class CanceledOperation{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "OPERATION_ID", nullable = false)
private Operation operation;
#Column(name = "RAINSON")
private String raison;
//getters & setters
}
I'm trying to join the operation and canceledIperation in order to display the raison for the canceled operation and null non canceled.
this is my native query in my repository:
#query(value = "SELECT op.* , co.raison as raison FROM operations op LEFT JOIN canceled_operations co ON op.ID = co.OPERATION_ID", countQuery = "SELECT count(*) FROM operations op LEFT JOIN canceled_operations co ON op.ID = co.OPERATION_ID")
Page<Operation> getAllOperations(Pageable pageable);
and this is how i call my repository:
final Page<Operation> operations= operationRepository.getAllOperations(pageable);
I tried many solution namely using the Object[] instead of Operation and I have also added an attribute called raison in the operation entity with #transient annotation, but even that I still get the raison is null.
can you please give me a hint to solve this issue.
thank in advance.
Well I found an easy way to do it, instead of doing a complicated query, I have used findAll() of each table then I affect the raison attribute from CanceledOperation to the message attribute of Operation using stream and map the two received lists from repositories.
In the CanceledOperation entity, can you give referencedColumnName = "ID" and try?
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "OPERATION_ID",referencedColumnName = "ID", nullable = false)
private Operation operation;

Spring Data method name for specific purpose

I'm new in Spring Data. I know that I can do it by #Query but I would like too learn more about how to write method names for specific purpose.
This is my Entity:
#Entity
#Table(name = "MEMBER_RECENT_CONTENT")
public class MemberRecentContent {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "MEMBER_ID", nullable = false)
private NCDMMember member;
#Column(name = "PRODUCT_ID", nullable = false)
private Long productId;
#Column(name = "VIEW_DATE", nullable = false)
private Date viewDate;
}
In my repository I have these two methods:
List<MemberRecentContent> findByMember_Id(Long memberId);
List<MemberRecentContent> findFirst10ByOrderByViewDateDesc();
Now I need to combine these two methods but I don't have any idea how to do this. I need to find 10 last record for specific MemberId. I have searched a lot but I couldn't find anything useful.
Maybe, you can define as below:
List<MemberRecentContent> findFirst10ByMemberId(Long memberId, Sort sort);
and then, you can use:
Sort sort = new Sort(Sort.Direction.DESC, "viewDate")
myRepository.findFirst10ByMemberId(memberId, sort)

How to join a table with one column of a view in that table's entity class

Scenario:
I have a products table with these fields: id, code, description, photo.
I have a product_stock view from other schema with fields: prod_code, stok_tot
Misson: What I need to do in Spring Boot is to join the products with their total stock.
Rules:
I can't use #Query on this task, I need to join them at the Product Entity Class, so that stock became some kind of Transient field of product.
I can't change the fact that product's ID is a Long and ProductStock's ID is a String, but I could use product's code field instead right? (how?)
So far... I tryed to use #OneToOne and #JoinColumn to do the job, but my REST gives me the stock field as NULL.
"Estoque.java"
#Entity
#Table(name = "VW_ESTOQUE", schema = "ASICAT")
public class Estoque {
#Id
#Column(name = "CD_BEM_SERVICO", unique = true, nullable = false)
private String codigo;
#Column(name = "ESTOQUE")
private Long estoque;
// getters and setters hidden by me
}
"Produto.java"
#Entity
#NamedEntityGraph(name = "Produto.detail", attributeNodes = #NamedAttributeNode("categorias"))
public class Produto implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String codigo;
private String descricao;
// here is where I get the null values
#Transient
#OneToOne(fetch = FetchType.LAZY)
#JoinTable(name = "VW_ESTOQUE", joinColumns = #JoinColumn(name = "CODIGO", referencedColumnName = "CODIGO"), inverseJoinColumns = #JoinColumn(name = "CD_BEM_SERVICO", referencedColumnName = "CODIGO"))
private Estoque estoque;
private String hash;
#ManyToMany(mappedBy = "produtos", fetch = FetchType.EAGER)
#BatchSize(size = 10)
private List<Categoria> categorias = new ArrayList<>();
// getters and setters hidden by me
}
In my product repository I call FindAll()
You have annotated Produto.estoque as #Transient, which means that it is not part of the persistent state of the entity. Such a field will be neither written nor read when instances of that entity are managed. That's not going to serve your purpose.
There are two things I can imagine you might have been trying to achieve:
That every time an Estoque is accessed via a Produto, it should be loaded from the DB to ensure its freshness. JPA does not provide for that, though you might want to annotate Estoque with #Cacheable(value = false), and specify the lazy fetch strategy on the Produto side of the relationship.
You want to avoid the persistence provider attempting to persist any changes to an Estoque, since it is backed by a view, not an updatable table. This we can address.
My first suggestion would be to map ASICAT.VW_ESTOQUE as a secondary table instead of an entirely separate entity. That might look something like this:
#Entity
#SecondaryTable(name = "VW_ESTOQUE", schema = "ASICAT"
pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "CD_BEM_SERVICO",
referencedColumnName = "CODIGO") })
public class Produto implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String codigo;
private String descricao;
#Column(name = "ESTOQUE", table = "VW_ESTOQUE", nullable = true,
insertable = false, updatable = false)
private Long estoque;
// ...
}
You might furthermore avoid providing a setter for the estoque property.
But the SecondaryTable approach might not work well if you cannot rely on the ESTOQUE view always to provide a row for every row of PRODUTO, as there will very likely be an inner join involved in retrievals. Moreover, you don't get lazy fetches this way. The main alternative is more or less what you present in your question: to set up a separate Estoque entity.
If you do set up a separate Estoque, however, then I would approach it a bit differently. Specifically,
I would make the relationship bidirectional, so that I could
make the Estoque entity the relationship owner.
Something like this, then:
#Entity
public class Produto implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String codigo;
private String descricao;
// must not be #Transient:
#OneToOne(fetch = FetchType.LAZY, mappedBy = "produto", cascade = {
CascadeType.REFRESH
})
private Estoque estoque;
// ...
}
#Entity
#Table(name = "VW_ESTOQUE", schema = "ASICAT")
#Cacheable(value = false)
public class Estoque {
#Id
#Column(name = "CD_BEM_SERVICO", nullable = false,
insertable = false, updatable = false)
private String codigo;
#Column(name = "ESTOQUE", insertable = false, updatable = false)
private Long estoque;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "CD_BEM_SERVICO", referencedColumnName = "CODIGO",
nullable = false, insertable = false, updatable = false, unique = true)
Produto produto;
// getters and setters hidden by me
}
In this case, I would avoid providing setter methods for any of the properties of Estoque, and avoid providing any constructor that allows initial property values to be set. Thus, to a first approximation, the only
way an instance's properties will take non-null values is if they are set by the persistence provider.
Additionally, since you mention Oracle, if you are using TopLink as your persistence provider then you might consider applying its #ReadOnly extension attribute to the Estoque entity, in place of or even in addition to some of these protections against trying to insert into or update the view.

Categories

Resources