Need to serializable entity on kotlin - spring

I have sombe BaseEntity
import org.springframework.data.util.ProxyUtils
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.MappedSuperclass
#MappedSuperclass
abstract class BaseEntity<T> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: T? = null
override fun equals(other: Any?): Boolean {
other ?: return false
if (this === other) return true
if (javaClass != ProxyUtils.getUserClass(other)) return false
other as BaseEntity<*>
return this.id != null && this.id == other.id
}
override fun hashCode() = 25
override fun toString(): String {
return "${this.javaClass.simpleName}(id=$id)"
}
}
And i have error message, but project are starting.
Class 'T' should implement 'java.io.Serializable'
I've heard that hibernate needs it. But I don't understand why he needs it. And I don't understand how I can do serialization of T

I fixed the problem by simply adding Serializable to type T.
BaseEntity<T: Serializable>
But I did not find out the reason for this error.
#MappedSuperclass
abstract class BaseEntity<T: Serializable> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: T? = null
override fun equals(other: Any?): Boolean {
other ?: return false
if (this === other) return true
if (javaClass != ProxyUtils.getUserClass(other)) return false
other as BaseEntity<*>
return this.id != null && this.id == other.id
}
override fun hashCode() = 25
override fun toString(): String {
return "${this.javaClass.simpleName}(id=$id)"
}
}

Related

identifier of an instance of ...was altered from

i found many response about this title "identifier of an instance of ...was altered from ..." but none of this give me a solution.
i am using PostgreSQL
with just 2 column id_type and libelle.
here is my Model level :
package com.stev.pillecons.pilleCons.models;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
#Entity(name = "type_pille")
#JsonIgnoreProperties({"hibernateLazyInitializer","handler"})
public class LePille {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private int id_type;
private String libelle;
public LePille(){}
public String getLibelle() {
return libelle;
}
public void setLibelle(String libelle) {
this.libelle = libelle;
}
public int getId_type() {
return id_type;
}
public void setId_type(int id_type) {
this.id_type = id_type;
}
}
My Service level :
#Override
public LePille updatePille(Integer id, LePille Sourcepille) {
Optional<LePille> existingSession = pilleRepo.findById(id);
if (existingSession.isPresent())
{
LePille Targetpile = existingSession.get();
BeanUtils.copyProperties(Sourcepille, Targetpile);
return pilleRepo.saveAndFlush(Targetpile);
}
else
{
throw new PilleException("pille not found");
}
}
when i debug it, with the data
{"id_type":10,"libelle":"dsf"}
with postman
the value of TargetPille is : {"id_type":10,"libelle":"dsf"}
and the value of SourcePille : {"id_type":0,"libelle":"popo"}
last but not least is Controller level:
#RequestMapping(value = "{id}", method = RequestMethod.PUT)
public ResponseEntity update(#PathVariable Integer id, #RequestBody LePille session) {
LePille updPille = pilleService.updatePille(id, session);
return new ResponseEntity<LePille>(updPille, HttpStatus.OK);
}
it is strange because juste update that not working, Create, Read and Delete works fine.
thanks in advance
i just change the code like this:
BeanUtils.copyProperties(Sourcepille, Targetpile, "id_type");
just add the id_type to ignore variable

springdatarepository ElasticSearch works on save but fails on update with java.lang.StackOverflowError: null

I am getting java.lang.StackOverflowError: null when updating my data through springdataelasticsearch. The application was generated using Jhipster 7.0.1. Spring Boot version is 2.4.4.. It works fine when creating a new object but an update results in error. Here is the relevant portion of CountryAdminUnitTypeService.java.
* Save a countryAdminUnitType.
*
* #param countryAdminUnitType the entity to save.
* #return the persisted entity.
*/
public CountryAdminUnitType save(CountryAdminUnitType countryAdminUnitType) {
log.debug("Request to save CountryAdminUnitType : {}", countryAdminUnitType);
CountryAdminUnitType result = countryAdminUnitTypeRepository.save(countryAdminUnitType);
countryAdminUnitTypeSearchRepository.save(result);
return result;
}
When creating, here's how the object looks on call to countryAdminUntiTypeSearchRepository.save(result);.
result = {CountryAdminUnitType#23268} "CountryAdminUnitType{id=200102, name='Province'}"
id = {Long#23292} 200102
name = "Province"
parent = null
country = {Country#23275} "Country{id=183778, name='Turkey', isoCode='TR'}"
id = {Long#23281} 183778
name = "Turkey"
isoCode = "TR"
countryAdminUnitTypes = null
And here's how it looks on updating.
result = {CountryAdminUnitType#24746} "CountryAdminUnitType{id=200102, name='Province'}"
id = {Long#24752} 200102
name = "Province"
parent = null
country = {Country#24754} "Country{id=183778, name='Turkey', isoCode='TR'}"
id = {Long#24756} 183778
name = "Turkey"
isoCode = "TR"
countryAdminUnitTypes = {PersistentSet#24761} size = 1
0 = {CountryAdminUnitType#24746} "CountryAdminUnitType{id=200102, name='Province'}"
id = {Long#24752} 200102
name = "Province"
parent = null
country = {Country#24754} "Country{id=183778, name='Turkey', isoCode='TR'}"
The only difference I see is that countryAdminUnitTypes is not null in second case. However this should be taken care of by JsonIgnoreProperties given in Country.java below.
Here's the beginning of a very long log file.
ERROR 91444 --- [ XNIO-5 task-1] c.s.c.s.CountryAdminUnitTypeService : Exception in save() with cause = 'NULL' and exception = 'null'
java.lang.StackOverflowError: null
at org.springframework.data.util.Streamable.stream(Streamable.java:87)
at org.springframework.data.util.Streamable.lambda$map$1(Streamable.java:101)
at org.springframework.data.util.LazyStreamable.stream(LazyStreamable.java:55)
at org.springframework.data.util.LazyStreamable.iterator(LazyStreamable.java:46)
at java.base/java.lang.Iterable.forEach(Iterable.java:74)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeCollectionValue(MappingElasticsearchConverter.java:710)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.getWriteComplexValue(MappingElasticsearchConverter.java:620)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeProperty(MappingElasticsearchConverter.java:601)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeProperties(MappingElasticsearchConverter.java:553)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeEntity(MappingElasticsearchConverter.java:511)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeEntity(MappingElasticsearchConverter.java:636)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.getWriteComplexValue(MappingElasticsearchConverter.java:627)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeProperty(MappingElasticsearchConverter.java:601)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeProperties(MappingElasticsearchConverter.java:553)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeEntity(MappingElasticsearchConverter.java:511)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeEntity(MappingElasticsearchConverter.java:636)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.getWriteComplexValue(MappingElasticsearchConverter.java:627)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.lambda$writeCollectionValue$7(MappingElasticsearchConverter.java:709)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at java.base/java.util.Spliterators$IteratorSpliterator.tryAdvance(Spliterators.java:1812)
at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.lambda$initPartialTraversalState$0(StreamSpliterators.java:294)
at java.base/java.util.stream.StreamSpliterators$AbstractWrappingSpliterator.fillBuffer(StreamSpliterators.java:206)
at java.base/java.util.stream.StreamSpliterators$AbstractWrappingSpliterator.doAdvance(StreamSpliterators.java:161)
at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.tryAdvance(StreamSpliterators.java:300)
at java.base/java.util.Spliterators$1Adapter.hasNext(Spliterators.java:681)
at java.base/java.lang.Iterable.forEach(Iterable.java:74)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeCollectionValue(MappingElasticsearchConverter.java:710)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.getWriteComplexValue(MappingElasticsearchConverter.java:620)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeProperty(MappingElasticsearchConverter.java:601)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeProperties(MappingElasticsearchConverter.java:553)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeEntity(MappingElasticsearchConverter.java:511)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeEntity(MappingElasticsearchConverter.java:636)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.getWriteComplexValue(MappingElasticsearchConverter.java:627)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeProperty(MappingElasticsearchConverter.java:601)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeProperties(MappingElasticsearchConverter.java:553)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeEntity(MappingElasticsearchConverter.java:511)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.writeEntity(MappingElasticsearchConverter.java:636)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.getWriteComplexValue(MappingElasticsearchConverter.java:627)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.lambda$writeCollectionValue$7(MappingElasticsearchConverter.java:709)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at java.base/java.util.Spliterators$IteratorSpliterator.tryAdvance(Spliterators.java:1812)
at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.lambda$initPartialTraversalState$0(StreamSpliterators.java:294)
at java.base/java.util.stream.StreamSpliterators$AbstractWrappingSpliterator.fillBuffer(StreamSpliterators.java:206)
at java.base/java.util.stream.StreamSpliterators$AbstractWrappingSpliterator.doAdvance(StreamSpliterators.java:161)
at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.tryAdvance(StreamSpliterators.java:300)
at java.base/java.util.Spliterators$1Adapter.hasNext(Spliterators.java:681)
at java.base/java.lang.Iterable.forEach(Iterable.java:74)
Here are the model classes.
CountryAdminUnitType.java
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.io.Serializable;
import javax.persistence.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.springframework.data.elasticsearch.annotations.FieldType;
/**
* A CountryAdminUnitType.
*/
#Entity
#Table(name = "country_admin_unit_type")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#org.springframework.data.elasticsearch.annotations.Document(indexName = "countryadminunittype")
public class CountryAdminUnitType implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JsonIgnoreProperties(value = { "parent", "country" }, allowSetters = true)
private CountryAdminUnitType parent;
#ManyToOne
#JsonIgnoreProperties(
value = { "defaultResidenceMeasurementUnit", "countryAdminUnitTypes", "preferences", "personNationalities" },
allowSetters = true
)
private Country country;
// jhipster-needle-entity-add-field - JHipster will add fields here
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public CountryAdminUnitType id(Long id) {
this.id = id;
return this;
}
public String getName() {
return this.name;
}
public CountryAdminUnitType name(String name) {
this.name = name;
return this;
}
public void setName(String name) {
this.name = name;
}
public CountryAdminUnitType getParent() {
return this.parent;
}
public CountryAdminUnitType parent(CountryAdminUnitType countryAdminUnitType) {
this.setParent(countryAdminUnitType);
return this;
}
public void setParent(CountryAdminUnitType countryAdminUnitType) {
this.parent = countryAdminUnitType;
}
public Country getCountry() {
return this.country;
}
public CountryAdminUnitType country(Country country) {
this.setCountry(country);
return this;
}
public void setCountry(Country country) {
this.country = country;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CountryAdminUnitType)) {
return false;
}
return id != null && id.equals(((CountryAdminUnitType) o).id);
}
#Override
public int hashCode() {
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
return getClass().hashCode();
}
// prettier-ignore
#Override
public String toString() {
return "CountryAdminUnitType{" +
"id=" + getId() +
", name='" + getName() + "'" +
"}";
}
}
Country.java
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.springframework.data.elasticsearch.annotations.FieldType;
/**
* A Country.
*/
#Entity
#Table(name = "country")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#org.springframework.data.elasticsearch.annotations.Document(indexName = "country")
public class Country implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "iso_code")
private String isoCode;
#OneToMany(mappedBy = "country")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#JsonIgnoreProperties(value = { "parent", "country" })
private Set<CountryAdminUnitType> countryAdminUnitTypes = new HashSet<>();
// jhipster-needle-entity-add-field - JHipster will add fields here
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Country id(Long id) {
this.id = id;
return this;
}
public String getName() {
return this.name;
}
public Country name(String name) {
this.name = name;
return this;
}
public void setName(String name) {
this.name = name;
}
public String getIsoCode() {
return this.isoCode;
}
public Country isoCode(String isoCode) {
this.isoCode = isoCode;
return this;
}
public void setIsoCode(String isoCode) {
this.isoCode = isoCode;
}
public Set<CountryAdminUnitType> getCountryAdminUnitTypes() {
return this.countryAdminUnitTypes;
}
public Country countryAdminUnitTypes(Set<CountryAdminUnitType> countryAdminUnitTypes) {
this.setCountryAdminUnitTypes(countryAdminUnitTypes);
return this;
}
public Country addCountryAdminUnitType(CountryAdminUnitType countryAdminUnitType) {
this.countryAdminUnitTypes.add(countryAdminUnitType);
countryAdminUnitType.setCountry(this);
return this;
}
public Country removeCountryAdminUnitType(CountryAdminUnitType countryAdminUnitType) {
this.countryAdminUnitTypes.remove(countryAdminUnitType);
countryAdminUnitType.setCountry(null);
return this;
}
public void setCountryAdminUnitTypes(Set<CountryAdminUnitType> countryAdminUnitTypes) {
if (this.countryAdminUnitTypes != null) {
this.countryAdminUnitTypes.forEach(i -> i.setCountry(null));
}
if (countryAdminUnitTypes != null) {
countryAdminUnitTypes.forEach(i -> i.setCountry(this));
}
this.countryAdminUnitTypes = countryAdminUnitTypes;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Country)) {
return false;
}
return id != null && id.equals(((Country) o).id);
}
#Override
public int hashCode() {
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
return getClass().hashCode();
}
// prettier-ignore
#Override
public String toString() {
return "Country{" +
"id=" + getId() +
", name='" + getName() + "'" +
", isoCode='" + getIsoCode() + "'" +
"}";
}
}
This is because of how JHipster sets up save. It was only happening on updating objects with bi-directional relationships.
* Save a countryAdminUnitType.
*
* #param countryAdminUnitType the entity to save.
* #return the persisted entity.
*/
public CountryAdminUnitType save(CountryAdminUnitType countryAdminUnitType) {
log.debug("Request to save CountryAdminUnitType : {}", countryAdminUnitType);
CountryAdminUnitType result = countryAdminUnitTypeRepository.save(countryAdminUnitType);
countryAdminUnitTypeSearchRepository.save(result);
return result;
}
The object to be updated is set correctly when being passed to jpa repository countryAdminUnitTypeRepository.save(countryAdminUnitType);. However, the returned result object has circular dependencies and is directly forwarded to elasticsearch repository countryAdminUnitTypeSearchRepository.save(result); for update. I resolved it by only using the id of the object.
Line
CountryAdminUnitType result = countryAdminUnitTypeRepository.save(countryAdminUnitType);
countryAdminUnitTypeSearchRepository.save(result);
has been changed to
CountryAdminUnitType result = countryAdminUnitTypeRepository.save(countryAdminUnitType);
countryAdminUnitType.setId(result.getId());
countryAdminUnitTypeSearchRepository.save(countryAdminUnitType);
return result;
Thank you for the discussion. This did not work for me on entity , using Jhipster 7.8.1.
FYI, I just commented out the SearchRepository.save(result); as a quick fix to unblock development.
Issue: https://github.com/jhipster/generator-jhipster/issues/16136#issuecomment-1012404392

Spring Data for Mongo DB does not throw DuplicateKeyException

I try to throw a DuplicateKeyException in a jUnit 5 Test. The unit test saves two objects to a repository with the same productId. productID is declared #Indexed( unique = true ) in the ProductEntity class. My expectation was to receive a DuplicateKeyException.
This is the test class:
package com.exercim.microservices.core.product;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.exercim.microservices.core.product.persistence.ProductEntity;
import com.exercim.microservices.core.product.persistence.ProductRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.dao.DuplicateKeyException;
#DataMongoTest
public class ProductRepositoryTest {
#Autowired
ProductRepository repository ;
#Test
public void testDuplicateKeyException() {
ProductEntity e1 = new ProductEntity( 1, "name", 1 ) ;
ProductEntity e2 = new ProductEntity( 1, "name", 1 ) ;
repository.deleteAll() ;
repository.save( e1 ) ;
assertThrows(DuplicateKeyException.class, () -> repository.save( e2 ) ) ;
}
}
This is the ProductEntitiy class:
package com.exercim.microservices.core.product.persistence;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
#Document( collection = "products" )
public class ProductEntity {
#Id
private String id ;
#Version
private Integer version ;
#Indexed( unique = true )
private int productId ;
private String name ;
private Integer weight ;
public ProductEntity() {
}
public ProductEntity( int productId, String name, int weight) {
this.productId = productId;
this.name = name;
this.weight = weight;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public int getVersion() {
return this.version;
}
public void setVersion(int version) {
this.version = version;
}
public int getProductId() {
return this.productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getWeight() {
return this.weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
The repository is an interface which extends PagingAndSortingRepository
This is part of my build.gradle file:
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-mongodb'
testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo'
The test fails with the message:
org.opentest4j.AssertionFailedError: Expected org.springframework.dao.DuplicateKeyException to be thrown, but nothing was thrown.
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:71)
Does anyone have an idea? Thanks in advance!
Thanks Joe!
The problem was, that auto index creation was not enabled. This was my original application.yml file:
spring.data.mongodb:
host: localhost
port: 27017
database: product-db
I added the following line:
auto-index-creation: true
That solved the issue.

how to convert integer to string using typeconverters in room android?

I am developing a chat app but I am getting the following error: Cannot find getter for the field.
that's why I want to convert Integer to String using type converters in Room but I did not find any sample below my User.java model class
#Entity public class User implements IChatUser {
#PrimaryKey(autoGenerate = true)
private Integer id;
#ColumnInfo(name = "name")
private String name;
#Ignore
Bitmap icon;
public User() {
}
#Ignore
public User(String name, Bitmap icon) {
this.name = name;
this.icon = icon;
}
#Override
public Integer getId() {
return this.id;
}
#Override
public String getName() {
return this.name;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
#Override
public Bitmap getIcon() {
return this.icon;
}
#Override
public void setIcon(Bitmap icon) {
this.icon = icon;
} }
below IchatUser.kt
interface IChatUser {
fun getId(): String
fun getName(): String?
fun getIcon(): Bitmap?
fun setIcon(bmp: Bitmap)
}
To create a #TypeConverter create a java class and call it IntConverter
public class IntConverter {
#TypeConverter
public static String toString(int number) {
return number == null ? null : Integer.toString(number);
}
#TypeConverter
public static int toInt(String str) {
return str == null ? null : Integer.parseInt(str);
}
}
Now you need the database to know about this TypeConverter so declare this:
#TypeConverters(IntConverter.class)
Just below the #Database annotation in the Database class

How does Spring's JPARepository and #Transactional behave together?

I have two methods (in a Spring boot application) that handle an entity. The entity has two fields, both boolean isDefault and isPdfGenerated. The first method (which is called from a controller) changes the isDefault flag when a new entity is created while the second one (called from a #Scheduled annotated method) changes the isPdfGenrated after it generates a pdf file for that entity.
My problem is that sometimes the second method finds entities with the isPdfGenerated flag set to false even though the file has been generated and saved in the database.
Both the methods have the #Transactional annotation and the repository interface for the entity extends JpARepository.
My guess is that the first method loads the entity from the database before the second method does but saves the entity after the second method does its job, thus overriding the isPdfGenerated flag.
Is this possible ? If the answer is yes, how should one handle such cases ? Shouldn't JPARepository handle the case when an entity gets updated from an external source ?
Bellow is some code to better illustrate the situation.
MyController:
#Controller
#RequestMapping("/customers")
public class MyController {
#Autowired
private EntityService entityService;
#RequestMapping(value = "/{id}/changeDefault", method = RequestMethod.POST)
public String changeDefault(#PathVariable("id") Long customerId, #ModelAttribute EntityForm entityForm, Model model) {
Entity newDefaultEntity = entityService.updateDefaultEntity(customerId, entityForm);
if (newDefaultEntity == null)
return "redirect:/customers/" + customerId;
return "redirect:/customers/" + customerId + "/entity/default;
}
}
EntityService:
import org.springframework.transaction.annotation.Transactional;
#Service
public class EntityService {
#Autowired
private EntityRepository entityRepository;
#Autowired
private CustomerRepository customerRepository;
#Transactional
public Entity updateDefaultEntity(Long customerId, submittedData) {
Customer customer = customerRepository.findById(customerId);
if(customer == null)
return customer; // I know there are better ways to do this
Entity currentDefaultEntity = entityRepository.findUniqueByCustomerAndDefaultFlag(customer, true);
if(currentDefaultEntity == null)
return null; // I know there are better ways to do this also
Entity newDefaultEntity = new Entity();
newDefaultEntity.setField1(submittedData.getField1());
newDefaultEntity.setField2(submittedData.getField2());
newDefaultEntity.setCustomer(customer);
oldDefaultEntity.setDefaultFlag(false);
newDefaultEntity.setDefaultFlag(true);
entityRepository.save(newDefaultEntity);
}
#Transactional
public void generatePdfDocument(Entity entity) {
Document pdfDocument = generateDocument(entity);
if(pdfDocument == null)
return;
documentRepository.save(pdfDocument);
entity.setPdfGeneratedFlag(true);
entityRepository.save(entity);
}
}
ScheduledTasks:
#Component
public class ScheduledTasks {
private static final int SECOND_IN_MILLISECONDS = 1000;
private static final int MINUTE_IN_SECONDS = 60;
#Autowired
private EntityRepository entityRepository;
#Autowired
private DocumentService documentService;
#Scheduled(fixedDelay = 20 * SECOND_IN_MILLISECONDS)
#Transactional
public void generateDocuments() {
List<Quotation> quotationList = entityRepository.findByPdfGeneratedFlag(false);
for(Entity entity : entitiesList) {
documentService.generatePdfDocument(entity);
}
}
}
DocumentService:
#Service
public class DocumentService {
#Autowired
private EntityRepository entityRepository;
#Autowired
private DocumentRepository documentRepository;
#Transactional
public void generatePdfDocument(Entity entity) {
Document pdfDocument = generateDocument(entity);
if(pdfDocument == null)
return;
documentRepository.save(pdfDocument);
entity.setPdfGeneratedFlag(true);
entityRepository.save(entity);
}
}
EntityRepository:
#Repository
public interface EntityRepository extends JpaRepository<Entity, Long> {
Entity findById(#Param("id") Long id);
List<Entity> findByPdfGeneratedFlag(#Param("is_pdf_generated") Boolean pdfGeneratedFlag);
Entity findUniqueByCustomerAndDefaultFlag(
#Param("customer") Customer customer,
#Param("defaultFlag") Boolean defaultFlag
);
}
DocumentRepository:
#Repository
public interface DocumentRepository extends JpaRepository<Document, Long> {
Document findById(#Param("id") Long id);
}
Entity:
#Entity
#Table(name = "entities")
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id")
public class Entity {
private Long id;
private boolean defaultFlag;
private boolean pdfGeneratedFlag;
private String field1;
private String field2;
private Customer customer;
public Entity() { }
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "is_default")
public boolean isDefaultFlag() {
return defaultFlag;
}
public void setDefaultFlag(boolean defaultFlag) {
this.defaultFlag = defaultFlag;
}
#Column(name = "is_pdf_generated")
public boolean isPdfGeneratedFlag() {
return pdfGeneratedFlag;
}
public void setPdfGeneratedFlag(boolean pdfGeneratedFlag) {
this.pdfGeneratedFlag = pdfGeneratedFlag;
}
#Column(name = "field_1")
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
#Column(name = "field_2")
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
#ManyToOne
#JoinColumn(name = "customer_id", referencedColumnName = "id", nullable = false)
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entity quotation = (Entity) o;
return id != null ? id.equals(entity.id) : entity.id == null;
}
#Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
#Override
public String toString() {
return "Entity{" +
"id=" + id +
", pdfGeneratedFlag=" + pdfGeneratedFlag +
", defaultFlag=" + defaultFlag +
", field1=" + field1 +
", field2=" + field2 +
", customer=" + (customer == null ? null : customer.getId()) +
"}";
}
}
I have omitted the other classes because they are either POJOs ( EntityForm ) or the same as other domain model classes ( Document ).
If you're talking about a row on the database that is getting updated by another process after the first process has read it but before it has been updated, then you need to put in some sort of optimistic locking strategy.
This will be handled by the underlying ORM api (e.g. Hibernate or Eclipselink) rather than Spring Data (which will just handle an optimistic locking errors thrown by the ORM).
Have a look at this article. Bear in mind that if you want optimistic locking you need some way of determining a row's version. In JPA this is normally done using a column annotated with the #Version tag.
https://vladmihalcea.com/hibernate-locking-patterns-how-does-optimistic-lock-mode-work/

Resources