In a spring-boot 3 app with spring-boot-starter-data-jpa
I have an entity:
@Entity
public class SomeJson {
public static final ObjectMapper MAPPER = new ObjectMapper();
public static final TypeReference<HashMap<String, Object>> MAP_REF = new TypeReference<HashMap<String, Object>>() {};
// some fields
public String jsonData;
@Transient
public HashMap<String, Object> data = new HashMap<String, Object>();
@Transient
private int initialHashCode = 0;
/**
* Converts JSON-Data to {@link HashMap}.
*/
@PostLoad
public void onLoad() {
try {
if (StringUtils.isNotEmpty(jsonData))
data.putAll( MAPPER.readValue(jsonData, MAP_REF) );
} catch (JacksonException ignore) {}
initialHashCode = data.hashCode();
}
/**
* Converts {@link HashMap} to JSON-String if dirty.
*/
@PreUpdate
public void onSave() {
System.out.println("**** " + initialHashCode + " == " + data.hashCode() );
if( initialHashCode == data.hashCode() ) return;
try {
jsonData = MAPPER.writeValueAsString(data);
} catch (JsonProcessingException e) {}
}
}
and the corresponding repo:
@Repository
public interface SomeJsonRepo extends JpaRepository<SomeJson, Long> {}
The @PostLoad
works just fine, but the code
SomeJson sj = someJsonRepo.findById(42);
sj.data.put("lastSuccessfulExport", System.currentTimeMillis());
// sj.onSave(); (1)
someJsonRepo.save(er);
log.info( "Authentication failed" );
does NOT trigger the @PreUpdate
method.
If I uncomment the line (1)
, I see that the onSave()
is executed twice:
**** -888829936 == 1673013342
2024-02-19T11:44:27.913+01:00 INFO 18408 --- [nio-8050-exec-1] some.Service : Authentication failed
**** -888829936 == 1673013342
Does JPA/Hibernate have troubles determining whether the instance is dirty and “forgetting” to fire the @PreUpdate
?
data
is@Transient
so doesn’t mark anything dirty so will not update anything.@M.Deinum how to extend the dirtyness-check to include a
@Transient
field?You don’t else it will also be persisted. Why aren’t you just mapping the
data
field to thejsonData
column and use a proper custom type to convert from/to the map. THen you don’t need all those@PostLoad
and@PreUpdate
mess. Or even better maybe use this which already provides aJsonType
.did it before in another project, and it’s not worth it. JSON custom type is not represented in java as a map, so marshaling must be done by hand
If you can do this manually you can do it in a type, without the callback mess.
Show 2 more comments