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/
Related
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.
These are my entities;
#Entity
public class IpAddress{
#Id
#Column(nullable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ")
#Getter
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
private final Application application;
.....
}
#Entity
public class Application{
#Column(nullable = false, updatable = false)
private final String applicationId;
#OneToMany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval = true)
private List<IpAddress> ipAddresses = new ArrayList<>();
}
#Entity
public class MerchantApplication {
#Id
#Column(nullable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ")
#Getter
private Long id;
#OneToOne
private final Merchant merchant;
#OneToOne(cascade = CascadeType.ALL)
private final Application application;
#ManyToOne(fetch = FetchType.LAZY)
private final User user;
}
this is the service method that I try to delete:
#Transactional
public void deleteIpAddressForMerchant(Long merchantId,Long ipAddressId) {
Merchant merchant = merchantRepository.getMerchant(merchantId);
MerchantApplication merchantApplication = merchant.getMerchantApplication();
if (Objects.isNull(merchantApplication))
throw new ApplicationNotFoundException();
if (merchantApplication.getApplication().getIpAddresses().size() == 1) {
throw new IpAddressCannotBeDeleted();
}
IpAddress ipAddress = ipAddressRepository.getByIdAndApplication(ipAddressId, merchantApplication.getApplication());
ipAddressRepository.delete(ipAddress);
}
it works fine when i remove this block in service method;
if (merchantApplication.getApplication().getIpAddresses().size() == 1) {
throw new IpAddressCannotBeDeleted();
}
But this way, it's not work.Can you help me , what is the problem ?
If you fetch an entity field which has orphanRemoval = true property in a #Transactional method, it will save this field with your changes on it when the method returns.
In your case, you fetch
merchantApplication.getApplication().getIpAddresses()
But you didn't any change on it, then the fetched list was saved as it is.
You can remove the element in the fetched list what you want to delete.
you programmatically throw an exception in the following code:
if (merchantApplication.getApplication().getIpAddresses().size() == 1)
{
throw new IpAddressCannotBeDeleted();
}
by default transactions are rolled back if a runtime exception happens inside the method that is not handled (try...catch). Does your IpAddressCannotBeDeleted class extend RuntimeException? If so, try adding the following attribute to your #Transactional annotation:
#Transactional(noRollbackFor={IpAddressCannotBeDeleted.class, ApplicationNotFoundException.class})
The `noRollbackFor´ attribute prevents Spring from rolling back the transaction if exceptions of those two types are being thrown in your method implementation.
I have a one to many relationship between "code" and "code system" (a code system has many codes.) I implemented my service with the following to save codes for code systems:
service
code = new Code();
code.setCode(valueConcept.getCode());
code.setName(valueConcept.getDisplay_name());
code.setValueSet(valueSet);
code.setCodeSystem(codeSystem);
code.setDateCreated(new Date());
code.setDateUpdated(new Date());
codeRepository.save(code);
The last line of the code is throwing the exception. "Code System" is an object which is created above using a similar JPA repository.
To implement this one to many relationship I have the following two entity classes:
Code.java
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "codeCode_idGenerator")
#Basic(optional = false)
#Column(name = "\"CODE_ID\"", nullable = false, insertable = false, updatable = false)
#SequenceGenerator(allocationSize = 1, name = "codeCode_idGenerator", sequenceName = "crisp_pophealth.application.code_id_seq", schema = "application", catalog = "crisp_pophealth")
public Integer getId() {
return this.id;
}
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY)
#org.hibernate.annotations.Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE })
#Basic(optional = true)
#JoinColumn(name = "\"CODE_SYSTEM_ID\"", nullable = true)
public CodeSystem getCodeSystem() {
return this.codeSystem;
}
CodeSystem.java
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "codeSystemRecord_idGenerator")
#Basic(optional = false)
#Column(name = "\"RECORD_ID\"", nullable = false)
#SequenceGenerator(allocationSize = 1, name = "codeSystemRecord_idGenerator", sequenceName = "crisp_pophealth.application.code_system_id_seq", schema = "application", catalog = "crisp_pophealth")
public Integer getId() {
return this.id;
}
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST,
CascadeType.MERGE }, mappedBy = "codeSystem")
#org.hibernate.annotations.Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE })
#Basic(optional = false)
#Column(name = "\"RECORD_ID\"", nullable = false)
public Set<Code> getCodes() {
return this.codes;
}
The reason the sequences are set the way they are is because the primary key is defined as follows on each postgres table (as an example):
CREATE TABLE application.code ("CODE_ID" integer NOT NULL DEFAULT nextval('application.code_id_seq'::regclass),
What's the best way annotate the classes for this relation to exist? I'm using the sequence strategy AUTO since the database is managing sequence values on creation. I've tried saving unsuccessfully using an entity manager, but I would prefer to have all persistence managed by these JPA repository classes.
I am trying to create a ManyToMany relation between DocumentModels, with an additionnal information in the relation (dosIndex)
#Entity
#Table(name = "T_DOCUMENT_MODELS_DMO")
public class TDocumentModelsDmo extends fr.axigate.nx.frontend.server.common.entity.ValidityPeriodEntity implements Serializable
{
#Id
#SequenceGenerator(name = "T_DOCUMENT_MODELS_DMO_DMOID_GENERATOR", sequenceName = "T_DMO_ID_SEQ")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "T_DOCUMENT_MODELS_DMO_DMOID_GENERATOR")
#Column(name = "DMO_ID", precision = 22)
private Long dmoId;
//Other unrelated members, no reference to TjDocumentSourcesDos
//constructors, getters and setters without annotations
}
#Entity
#Table(name = "TJ_DOCUMENT_SOURCES_DOS")
public class TjDocumentSourcesDos implements Serializable
{
#Column(name = "DOS_INDEX", nullable = false, precision = 22)
private long dosIndex; //the additionnal info on the relation
#EmbeddedId
private TjDocumentSourcesDosPK id = new TjDocumentSourcesDosPK();
#ManyToOne
#MapsId("dosParentId")
#JoinColumn(name = "DOS_PARENT_ID", nullable = false, insertable = false, updatable = false)
private TDocumentModelsDmo TDocumentModelsDmoParent;
#ManyToOne
#MapsId("dosSourceId")
#JoinColumn(name = "DOS_SOURCE_ID", nullable = false, insertable = false, updatable = false)
private TDocumentModelsDmo TDocumentModelsDmoSource;
//constructors, getters and setters without annotations
}
#Embeddable
public class TjDocumentSourcesDosPK implements Serializable
{
#Column(name = "DOS_PARENT_ID", nullable = false, precision = 22)
private Long dosParentId;
#Column(name = "DOS_SOURCE_ID", nullable = false, precision = 22)
private Long dosSourceId;
//constructors, getters and setters without annotations
//hashCode and equals implemented
}
I can insert datas in both tables, but when I try to request it using an entityManager, i get something strange :
Query query = entityManager.createQuery("SELECT dos.TDocumentModelsDmoSource FROM TDocumentModelsDmo AS dmo, TjDocumentSourcesDos as dos WHERE dmo.dmoId = :modelId AND dos.TDocumentModelsDmoParent = dmo");
query.setParameter("modelId", someData);
ArrayList<TjDocumentSourcesDos> dosList = (ArrayList<TjDocumentSourcesDos>) query.getResultList();
will work, while the following will throw an exception : QuerySyntaxException: dos.TDocumentModelsDmoSource is not mapped
Query query = entityManager.createQuery("SELECT sources FROM TDocumentModelsDmo AS dmo, TjDocumentSourcesDos as dos, dos.TDocumentModelsDmoSource AS sources WHERE dmo.dmoId = :modelId AND dos.TDocumentModelsDmoParent = dmo");
query.setParameter("modelId", someData);
ArrayList<TjDocumentSourcesDos> dosList = (ArrayList<TjDocumentSourcesDos>) query.getResultList();
This prevents me from doing more complicated requests where I would use my sources models in the WHERE condition.
I tried adding a referencedColumnName = "DMO_ID" in both my JoinColumn annotations, but I still get the same error
I have a relationship n/n between Product and Order
So I have a third table ProductOrder, because I need new columns when they are created.
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ORDER_SEQ")
#Column(unique = true, nullable = false, updatable = false)
private Long idOrder;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ProductOrder> productOrder;
//get and setter
here is the ProductOrder:
#Entity
#IdClass(ProductOrderId.class)
public class ProductOrder implements Serializable {
private static final long serialVersionUID = 3943799614725570559L;
#Id
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "product_id")
private Product product;
#Id
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "order_id")
private Order order;
private Integer qtdProduct;
private Double unitValueProductOrder;
//get and setter
also My ProcutOrderId (just in case)
public class ItemCompraId implements Serializable {
private Long compra;
private Long produto;
//get and set
and my Order entity:
#Entity
#SequenceGenerator(name = "ORDER_SEQ", sequenceName = "s_compra", initialValue = 1, allocationSize = 1)
public class Order implements Serializable {
private static final long serialVersionUID = 3943799614725570559L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ORDER_SEQ")
#Column(unique = true, nullable = false, updatable = false)
private Long idOrder;
private Double orderValue;
private Date creatingDate;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ProductOrder> productOrder;
So basically I have any Products ALREADY persisted in db... I just got a list of them when some order is about to be ordered. So I wanna persist a new object (Order) based on some already persisted object (products). This is the method invoked on managedbean to persist an Order.
public String doOrder() throws Exception {
try {
Order order = new Order();
compra.setCreatingDate(new Date());
compra.setOrderValue(null);
if (compra.getProductOrder() == null)
compra.setProductOrder(new HashSet<ProductOrder>());
for (Product product : listOfMyCartOfProducts) {
ProductOrder productOrder = new ProductOrder();
productOrder.setQtdProduct(100);
productOrder.unitValueProductOrder(null);
productOrder.setOrder(order);
productOrder.setProduct(product); //I THINK THAT THE PROBLEM IT'S HERE
order.getOrderProduct().add(productOrder);
}
ejbInvoke.persist(order); //tryed .merge and it doesn't work aswell
return "stuff";
Any ideas?
I'm desperate.. I need this working for yesterday..
Any help please??
Btw I'm using JSF 2.0, Hibernate with JPA 2.0 and Postgres.
Regards,
You have set the order->productOrder->product relationships to cascade the persist (included in cascadeType.ALL). When you call persist on order, you are in effect calling persist on ProductOrder and Product as well, which is expected to throw an exception if any of them already exist in the database.
Either
1) remove the cascade persist option on the productOrder->product relationship so that persist is not getting called - with the draw back that you will have to manually call persist if you ever associate new Products through a new Order.
2) Call em.find using the product pk, and associate the instance returned to the productOrder->product relationship
3) use em.merge instead which will cascade over each relationship and decide on its own if the entity exists or needs to be inserted. This will cause changes made within the product instance though to be merged as well.