convertToDatabaseColumn when data is not being persisted? - java

I have implemented method AttributeConverter.convertToEntityAttribute to load json data from the db. I am not trying to persist data, but for some reason convertToDatabaseColumn is being called.
This is what happens:
1. I call a repository method
2. then a call to AttributeConverter.convertToEntityAttribute follows -> returns a list of entity Cx. Till this point everything is normal.
3. But for some reason AttributeConverter.convertToDatabaseColumn is called right after, with that same list of entity Cx as argument -> returns stringV
4.Now convertToEntityAttribute is called again with stringV as argument, which is also strange.
Could it be that a #OneToOne relation is causing this? Why is this executing convertToDatabaseColumn if I'm not persisting an entity, at least explicitly?
All of this happens just by calling a single method in one of my repository classes:
Here is the code
public interface RSTRepository extends CrudRepository<RST, Long> {
List<RST> findByDuctNameIgnoreCase(String ductName);
}
#Entity
#Table(name="r_s_t")
public class RST {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#OneToOne
#JoinColumn(name = "r_s_id")
private Rs rs;
#Column(name = "channel")
private String channelName;
...
}
#Entity
#Table(name="r_s")
public class RS {
#Id
#Column(name = "rs_id", columnDefinition = "json")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name = "c_x", columnDefinition = "json")
#Convert(converter = JsonToCxConverter.class)
private List<Cx> cxs;
...
}
public class Cx {
private Long someId;
private List<Long> values;
...
}
#Converter
public class JsonToCxConverterimplements AttributeConverter<List<Cx>, String>{
//this gets executed
#Override
public String convertToDatabaseColumn(List<Cx> entityAttribute) {
ObjectMapper objectMapper = new ObjectMapper();
log.info("--------------------");
return "";
}
#Override
public List<Cs> convertToEntityAttribute(String dbData) {
if (dbData == null || dbData.isEmpty()) return Collections.emptyList();
//... uses the object mapper to parse the json and return a simple object.
...
}
Like I said, this happens when calling RSTRepository.findByDuctNameIgnoreCase

Yes its really behaving like you are saying. Also when persisting RST, Converter is also called 3x.
It also called 3x when reading just RS entity, i.e. it is not caused by #OneToOne relation.
I think it is how hibernate works. It should not be a problem, you get the right data without error.
From stacktrace I see that second and third call is from AbstractRowReader.performTwoPhaseLoad().
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:241)
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:209)
at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133)
I think its something that cannot be disabled. From hibernate sources I see that entity is registered to "hydrating". I found more about it here https://stackoverflow.com/a/29538797/2044957
Another thing: This is happening only when using converter on a collection. Converter is called once if it used on single type, for example AttributeConverter<String, String>.

Related

Updating only relevant entities in aggregates with #ColumnTransformer

In our spring boot application, I am trying to save an aggregate, that consists of a root entity (ParentEntity) and a Set of child entities (ChildEntity).
The intention is, that all operations are done through the aggreate. So there is no need for a repository for ChildEntity, as the ParentEntity is supposed to manage all save or update operations.
This is how the Entities look like:
#Entity
#Table(name = "tab_parent", schema = "test")
public class ParentEntity implements Serializable {
#Id
#Column(name = "parent_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer parentId;
#Column(name = "description")
private String description;
#Column(name = "created_datetime", updatable = false, nullable = false)
#ColumnTransformer(write = "COALESCE(?,CURRENT_TIMESTAMP)")
private OffsetDateTime created;
#Column(name = "last_modified_datetime", nullable = false)
#ColumnTransformer(write = "COALESCE(CURRENT_TIMESTAMP,?)")
private OffsetDateTime modified;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "ParentEntity")
private Set<ChildEntity> children;
// constructor and other getters and setters
public void setChildren(final Set<ChildEntity> children) {
this.children = new HashSet<>(children.size());
for (final ChildEntity child : children) {
this.addChild(child);
}
}
public ParentEntity addChild(final ChildEntity child) {
this.children.add(child);
child.setParent(this);
return this;
}
public ParentEntity removeChild(final ChildEntity child) {
this.children.add(child);
child.setParent(null);
return this;
}
}
#Entity
#DynamicUpdate
#Table(name = "tab_child", schema = "test")
public class ChildEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "child_id")
private Integer childId;
#Column(name = "language_id")
private String languageId;
#Column(name = "text")
private String text;
#Column(name = "created_datetime", updatable = false, nullable = false)
#ColumnTransformer(write = "COALESCE(?,CURRENT_TIMESTAMP)")
public OffsetDateTime created;
#Column(name = "last_modified_datetime", nullable = false)
#ColumnTransformer(write = "COALESCE(CURRENT_TIMESTAMP,?)")
public OffsetDateTime modified;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id", updatable = false)
private ParentEntity parent;
// constructor and other getters and setters
public ParentEntity getParent() {
return this.parent;
}
public void setParent(final ParentEntity parent) {
this.parent = parent;
}
}
This is the store method to save or update the entities:
public Integer merge(final ParentDomainObject parentDomainObject) {
final ParentEntity parentEntity =
this.mapper.toParentEntity(parentDomainObject);
final ParentEntity result = this.entityManager.merge(parentEntity);
this.entityManager.flush();
return result.getParentId();
}
And this is the store method to retrieve the aggregate by id:
public Optional<ParentDomainObject> findById(final Integer id) {
return this.repo.findById(id).map(this.mapper::toParentDomainObject);
}
As you can see our architecture strictly separates the store from the service layer. So the service only knows about domain objects and does not depend on Hibernate Entites at all.
When updating either the child or the parent, firstly the parent is loaded. In the service layer, the domain object is updated (fields are set, or a child is added/removed).
Then the merge method (see code snippet) of the store is called with the updated domain object.
This works, but not completely as we want to. Currently every update leads to the parent and EVERY chhild entity being saved, even if all field remained the same. We added the #DynamicUpdate annotaton. Now we saw, that the "modified" field is the problem.
We use a #ColumnTransformer to have the database set the date. Now even if you call the services update method without changing anything, Hibernate generates a update query for EVERY object, which updates only the modified field.
The worst thing about that is, as every object is saved, every modified date changed as well to the current date. But we need information about exactly which object really changed and when.
Is there any way to tell hibernate, that this column should not be taken into account when deciding what to update. However of course, if a field changed, the update operation should indeed update the modified field.
UPDATE:
My second approach after #Christian Beikov mentioned the use of #org.hibernate.annotations.Generated( GenerationTime.ALWAYS )
is the following:
Instead of #Generated (which uses #ValueGenerationType( generatedBy = GeneratedValueGeneration.class )),
I created my own annotations, which use custom AnnotationValueGeneration implementations:
#ValueGenerationType(generatedBy = CreatedTimestampGeneration.class)
#Retention(RetentionPolicy.RUNTIME)
public #interface InDbCreatedTimestamp {
}
public class CreatedTimestampGeneration
implements AnnotationValueGeneration<InDbCreatedTimestamp> {
#Override
public void initialize(final InDbCreatedTimestamp annotation, final Class<?> propertyType) {
}
#Override
public GenerationTiming getGenerationTiming() {
return GenerationTiming.INSERT;
}
#Override
public ValueGenerator<?> getValueGenerator() {
return null;
}
#Override
public boolean referenceColumnInSql() {
return true;
}
#Override
public String getDatabaseGeneratedReferencedColumnValue() {
return "current_timestamp";
}
}
#ValueGenerationType(generatedBy = ModifiedTimestampGeneration.class)
#Retention(RetentionPolicy.RUNTIME)
public #interface InDbModifiedTimestamp {
}
public class ModifiedTimestampGeneration
implements AnnotationValueGeneration<InDbModifiedTimestamp> {
#Override
public void initialize(final InDbModifiedTimestamp annotation, final Class<?> propertyType) {
}
#Override
public GenerationTiming getGenerationTiming() {
return GenerationTiming.ALWAYS;
}
#Override
public ValueGenerator<?> getValueGenerator() {
return null;
}
#Override
public boolean referenceColumnInSql() {
return true;
}
#Override
public String getDatabaseGeneratedReferencedColumnValue() {
return "current_timestamp";
}
}
I use these annotations in my entities instead of the #ColumnTransformer annotations now.
This works flawlessly when I insert a new ChildEntity via addChild(), as now not all timestamps of all entities of the aggregate are updated anymore. Only the timestamps of the new child are set now.
In other words, the InDbCreatedTimestamp works as it should.
Sadly, the InDbModifiedTimestamp does not. Because of GenerationTiming.ALWAYS, I expected the timestamp to be generated on db level, everytime an INSERT OR UPDATE is issued. If I change a field of a ChildEntity and then save the aggregate, an update statement is generated only for this one database row, as expected. However, the last_modified_datetime column is not updated, which is surprising.
It seems that this is unfortunately still an open bug. This issue describes my problem precisely: Link
Can someone provide a solution how to get this db function executed on update as well (without using db triggers)
You could try to use #org.hibernate.annotations.Generated( GenerationTime.ALWAYS ) on these fields and use a database trigger or default expression to create the value. This way, Hibernate will never write the field, but read it after insert/update.
Overall this has a few downsides though (need the trigger, need a select after insert/update), so I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO/domain model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(ParentEntity.class)
#UpdatableEntityView
public interface ParentDomainObject {
#IdMapping
Integer getParentId();
OffsetDateTime getModified();
void setModified(OffsetDateTime modified);
String getDescription();
void setDescription(String description);
Set<ChildDomainObject> getChildren();
#PreUpdate
default preUpdate() {
setModified(OffsetDateTime.now());
}
#EntityView(ChildEntity.class)
#UpdatableEntityView
interface ChildDomainObject {
#IdMapping
Integer getChildId();
String getName();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ParentDomainObject a = entityViewManager.find(entityManager, ParentDomainObject.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<ParentDomainObject> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary! It also supports writing/mapping back to the persistence model in an efficient manner. Since it does dirty tracking for you, it will only flush changes if the object is actually dirty.
public Integer merge(final ParentDomainObject parentDomainObject) {
this.entityViewManager.save(this.entityManager, parentDomainObject);
this.entityManager.flush();
return parentDomainObject.getParentId();
}

Spring JPA exception while inserting Json value (using hibernate-types-52)

I'm using hibernate-types-52 by Vlad Mihalcea together with Spring JPA to insert a POJO as a Json value into my Postgresql database.
My entity is defined this way:
#Entity
#Table(name = "hoshin_kanri")
#TypeDef(
name = "jsonb",
typeClass = JsonBinaryType.class
)
public class HKEntity {
#Id
#Column(name = "id_ai", columnDefinition = "bigint")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id_ai;
#Column(name = "id_hk", columnDefinition = "bigint")
private Integer id_hk;
#Type(type = "jsonb")
#Column(name = "hk_data", columnDefinition = "jsonb")
private HKData hk_data;
public HKEntity(Integer id_hk, HKData hk_data) {
this.id_hk = id_hk;
this.hk_data = hk_data;
}
And this is the POJO:
public class HKData {
private String name;
private Year targetYear;
private String description;
public HKData(String name, Year targetYear, String description) {
this.name = name;
this.targetYear = targetYear;
this.description = description;
}
I've defined a Repository interface to query the objects into the database:
public interface HKRepository extends CrudRepository<HKEntity, Integer> {
#Query(value = "INSERT INTO 'hk_data' VALUES :Entity", nativeQuery = true)
void test_json(#Param("Entity") HKEntity e);
}
and a test Service just to see if it's working properly:
#Service
public class HKService {
#Autowired
HKRepository hk_repository;
public String json_test() {
HKData d = new HKData("Prova", Year.now(), "Descrizione");
HKEntity e = new HKEntity(1,d);
hk_repository.test_json(e);
return "Value created";
}
}
However, i keep getting the following exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.ehk.rest.entity.HKEntity
I've tried many fixes suggested for this error, but i cannot understand the nature of the error itself. What is wrong with this approach? Beside a tip for fixing this, i would like to understand why this error is originated.
The error means that there's an instance of the HKEntity entity which is referenced from somewhere in the current Hibernate session, and you've neither explicitly persisted this instance, nor instructed Hibernate to persist it cascadly. It's hard to say what exactly is going on, but there are some issues with your code that might have confused either Spring Data JPA framework, or the Hibernate itself.
First, the Spring's CrudRepository interface already has a save() method, so you could use it instead of your test_json() method.
I also see no reason in inserting a Hibernate entity with a native query, and I don't even think this is a valid query. Your test_json() method tries to natively insert an HKEntity entity into the hk_data table, but the HKEntity entity should be saved into the hoshin_kanri table, according to your mapping.
So I would change your service code as follows:
public String json_test() {
HKData d = new HKData("Prova", Year.now(), "Descrizione");
HKEntity e = new HKEntity(1,d);
hk_repository.save(e);
return "Value created";
}

Automatically convert Spring JPA Entity OneToMany to List<ID> in DTO and back (ModelMapper)

Animal.java
#Data
#Entity
public class Animal implements MyEntityInterface {
public enum Sex {MALE, FEMALE}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private Sex sex;
private boolean castrated;
#OneToMany
private List<Symptom> symptoms;
}
AnimalDTO.java
#Getter
#Setter
public class AnimalDTO implements Serializable {
private long id;
private String name;
private Animal.Sex sex;
private boolean castrated;
private List<Long> symptoms;
}
I wish for a list of Symptoms to be automatically mapped to a list of ID's. This could be achieved in many ways, such as creating a TypeMap, creating a Converter or even just by creating a method in AnimalDTO.java:
public void setSymptoms(List<Symptom> symptoms) {
if (symptoms != null)
this.symptoms = symptoms.stream().map(s -> s.getId()).collect(Collectors.toList());
}
But now imagine it's not only Symptoms, but 50 other fields too. That's a lot of code for the same functionality. And then, it's not only Animal to AnimalDTO, but another 30 different classes with their respective DTOs too.
Also, that still leaves the way back open. From ID to entity. This can (in theory) be achieved easily with the following pseudocode:
List<EntityMemberField.class> list;
for (var entityid : listOfEntityIDsOfDto) {
Object persistedObject = entityManager.find(EntityMemberField.class, entityid);
list.add(persistedObject);
}
...
ModelMapperDestination.setField(list);
This is the same for absolutely every Entity/DTO and should automatically happen for every Entity relationship where the Entity implements MyEntityInterface.
An idea how I could achieve that would be overriding MappingEngineImpl.java from ModelMapper which I register as a Spring Service and inject the EntityManager into, but how could I get ModelMapper to use mine? Or is there maybe an easier way?
The goal is to have a fairly automated conversion from Spring Entities to their corresponding DTO by... just calling modelMapper.map(entity, EntityDTO.class);

Getting infinite Json response when using many to one mapping in spring [duplicate]

This question already has answers here:
Infinite Recursion with Jackson JSON and Hibernate JPA issue
(29 answers)
Closed 3 years ago.
I am trying to create Many to one mapping between two entities in spring. However when I try to fetch the values using a restController I get
Java.lang.IllegalStateException: Cannot call sendError() after the
response has been committed
error and an infinite JSON response. Adding JSON ignore solves this issue but then I don't get that column in my response at all. I don't know how to fix this. Please help. Thanks in advance.
Here are my entities:
#Entity
#Table(name="tbl1")
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "customer")
private String customer;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="data_id", insertable = true, nullable = false, updatable = false)
private DataList dataList1;
}
#Entity
#Table(name="tbl2")
public class DataList {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="data_id")
private Long dataId;
#Column(name="user_name")
private String userName;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,mappedBy = "dataList1")
private List<Data> data1;
}
Repositories:
#Repository
#Transactional
public interface DataList extends JpaRepository<DataList,Long> {
}
#Repository
#Transactional
public interface Data extends JpaRepository<Data,Long> {
}
My error: Java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
1) In general putting fetch = FetchType.EAGER on #OneToMany is not an advisable practice. Try to set it to LAZY instead.
2) Make sure that Data and DataList entities properly implement equals() and hashCode() methods.
It is happening as JSON serializer is trying to serialize both the entities recursively again and again.
So, while serializing Data, its member dataList1 is of type DataList which further contains List<Data>, this loop will be infinite.
Ideally, in such scenarios, the entities should be mapped to some other model meant for the serialization and response or one of the model needs to be #JsonIgnore to avoid this recursive loop going.
EAGER is a bad practice. Use LAZY, and when you need the related entities, use fetch join query.
Most likely the problem here in the bi-directional relation. You fetch DataList with Data list. And each Data in List ListData refers again.
More likely here to be a stack overflow when serializing json. So remember one rule: never give in controllers hibernate entities. Write a mapper for map this entities to Dto objects.
You may think that it is extremely boring to map some models to another. But here in another way. Hibernate entities should not be passed to front end. My application uses several types of objects: Entities (when fetch from DB), DTO (When map entities to DTO and give their in service components), Shared (when map DTO to Shared and share as a response to the controller between my microservices) and Model (when map from Response to Model and give to the template engine, for example freemarker). You may not need this hierarchy of models.
Create DTO's:
#AllArgsConstructor
#NoArgsConstructor
#Getter
public class DataListDto {
private Long dataId;
private String userName;
private List<DataDto> data1;
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
public class DataDto {
private long id;
private String customer;
}
write your mapper:
public class DataListMapper {
public static DataListDto mapToDto(DataList dataList) {
return new DataListDto(dataList.getDataId(), dataList.getUserName(), dataList.getData1().stream().map(x -> DataListMapper.mapToDto(x).collect(Collectors.toList)))
}
public static DataDto mapToDto(Data data) {
return new DataDto(data.getId(), data.getCustomer());
}
}
your service:
public class DataListService {
#Autowired
private DataListRepository dataListRepository;
public List<DataListDto> getAllData() {
List<DataList> dataLists = this.dataListRepository.findAll();
return dataLists.stream().map(x->DataListMapper.mapToDto(x)).collect(Collectors.toList());
}
}

Jersey ClientResponse Get List of Composite Entities

I am trying to get a Result of a List, basically a list of entities using Jersey RESTful API (Server and Client)
UserRESTClient client = new UserRESTClient();
ClientResponse response = client.getUsersByType(ClientResponse.class, String.valueOf(userType));
List<User> participants = response.getEntity(new GenericType<List<User>>() {
});
However, the above code does not work if Entity User has a Composite Object, if for instance,
public class User {
private UserId userId;
}
public class UserId {
private int id;
private int categoryId;
}
In this case, the JSON is deserialized by Jersey and returned null for the field type UserId inside Class User. I inspected the JSON returned and everything seems good at the RESTful Server end, but the nested JSON response is not clearly processed at the Client.
Any help would be greatly appreciated. I am not sure if it because of the Jackson preprocessor.
Following is the actual Code Snippet. It involves two classes Participant and ParticipantPK (primary for each Participant).
#Entity
#Table(name = "conference_participant")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Participant.findAll", query = "SELECT p FROM Participant p"),
public class Participant implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected ParticipantPK participantPK;
}
#Embeddable
public class ParticipantPK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "conference_id")
private int conferenceId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 150)
#Column(name = "participant_sip_uri")
private String participantSipUri;
public ParticipantPK() {
}
public ParticipantPK(int conferenceId, String participantSipUri) {
this.conferenceId = conferenceId;
this.participantSipUri = participantSipUri;
}
And the Code for retrieving ClientResponse,
List<Participant> participants = response.getEntity(new GenericType<List<Participant>>() {
});
However, the ParticipantPK (Composite PK) is null.
You only pasted a code snippet so I don't know if this part is excluded, but in my code I didn't have setters for the fields. I had getters, but no setters.
Without the setters, my composite objects themselves were non-null, but the members of those objects were themselves null.
I tried to reproduce it, but using the same data structures worked for me. What version of Jersey are you using? Is User class annotated with #XmlRootElement or are you using the POJO mapping feature?

Categories