#LastModifiedDate doesn't work when only nested nested object is updated - spring-boot

I have a Unit entity and I'm using the #LastModifiedDate annotation to keep track of the updates. The problem is that in case I only update the items field the updateDate field isn't updated with the new date but if I update any other fields in the Unit entity the updateDate field is updated properly.
//other annotations
#EntityListeners(AuditingEntityListener.class)
public class Unit {
#Id
#GeneratedValue
private UUID id;
private String unitId;
private String unitName;
//other fields
#LastModifiedDate
private LocalDateTime updateDate;
#OneToMany(cascade = {CascadeType.ALL, CascadeType.REFRESH})
#OrderBy("slotNumber")
private List<Item> items;
}
Unit repository
public interface UnitRepo extends CrudRepository<Unit, String> {
Set<Unit> findAllByProfileUsername(String username);
}
And my update method in my Unit service
public Unit updateUnit(Unit unit) {
return repo.save(unit);
}

#LastModifiedDate will only update the modified date when the changes have been made in the entity parameters, not the relationship. When you only modify the Item, updateDate will not be updated. You may find the open issue(for mongo) related to the same.
In case you want to modify the updateDate, you may implement the entity listeners with the #PrePersist or #PreUpdate (see JPA Lifecycle). You may also have a look into AuditorAware

You can use the callback methods in your Unit Entity class.which allows to detect the changes before / after to the entity class. #PreUodate and #PostUpdate you can try.
References -
https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/listeners.html

Related

Document field as primary key not working

I have a "document" field that needs to be a primary key and must be unique, but every time I do a POST with the same document it updates the document and doesn't send a BAD_REQUEST
My entity:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(uniqueConstraints={#UniqueConstraint(columnNames={"document"})})
public class Cliente {
#Id
#Column(unique=true, updatable = false)
#NotBlank #NotNull
private String document;
#NotBlank
private String name;
#NotNull
private LocalDateTime date;
}
When I try to make a new POST with the same document it just updates what is saved in the database.
"Hibernate: update client set date=?, name=? where document=?"
The problem is that Spring Data JPA, when you call Repository#save, assumes that you want to update an existing entity when the passed in entity object has the id attribute set. You will have to inject a EntityManager in your code and instead call EntityManager#persist if you want to make sure that Hibernate tries to do an insert, in which case you'd get a constraint violation exception, just as you expect.

Spring Data Rest - sort by nested property

I have a database service using Spring Boot 1.5.1 and Spring Data Rest. I am storing my entities in a MySQL database, and accessing them over REST using Spring's PagingAndSortingRepository. I found this which states that sorting by nested parameters is supported, but I cannot find a way to sort by nested fields.
I have these classes:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
#ManyToOne
protected Address address;
#ManyToOne(targetEntity = Name.class, cascade = {
CascadeType.ALL
})
#JoinColumn(name = "NAME_PERSON_ID")
protected Name name;
#Id
protected Long id;
// Setter, getters, etc.
}
#Entity(name = "Name")
#Table(name = "NAME")
public class Name{
protected String firstName;
protected String lastName;
#Id
protected Long id;
// Setter, getters, etc.
}
For example, when using the method:
Page<Person> findByAddress_Id(#Param("id") String id, Pageable pageable);
And calling the URI http://localhost:8080/people/search/findByAddress_Id?id=1&sort=name_lastName,desc, the sort parameter is completely ignored by Spring.
The parameters sort=name.lastName and sort=nameLastName did not work either.
Am I forming the Rest request wrong, or missing some configuration?
Thank you!
The workaround I found is to create an extra read-only property for sorting purposes only. Building on the example above:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
// read only, for sorting purposes only
// #JsonIgnore // we can hide it from the clients, if needed
#RestResource(exported=false) // read only so we can map 2 fields to the same database column
#ManyToOne
#JoinColumn(name = "address_id", insertable = false, updatable = false)
private Address address;
// We still want the linkable association created to work as before so we manually override the relation and path
#RestResource(exported=true, rel="address", path="address")
#ManyToOne
private Address addressLink;
...
}
The drawback for the proposed workaround is that we now have to explicitly duplicate all the properties for which we want to support nested sorting.
LATER EDIT: another drawback is that we cannot hide the embedded property from the clients. In my original answer, I was suggesting we can add #JsonIgnore, but apparently that breaks the sort.
I debugged through that and it looks like the issue that Alan mentioned.
I found workaround that could help:
Create own controller, inject your repo and optionally projection factory (if you need projections). Implement get method to delegate call to your repository
#RestController
#RequestMapping("/people")
public class PeopleController {
#Autowired
PersonRepository repository;
//#Autowired
//PagedResourcesAssembler<MyDTO> resourceAssembler;
#GetMapping("/by-address/{addressId}")
public Page<Person> getByAddress(#PathVariable("addressId") Long addressId, Pageable page) {
// spring doesn't spoil your sort here ...
Page<Person> page = repository.findByAddress_Id(addressId, page)
// optionally, apply projection
// to return DTO/specifically loaded Entity objects ...
// return type would be then PagedResources<Resource<MyDTO>>
// return resourceAssembler.toResource(page.map(...))
return page;
}
}
This works for me with 2.6.8.RELEASE; the issue seems to be in all versions.
From Spring Data REST documentation:
Sorting by linkable associations (that is, links to top-level resources) is not supported.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting
An alternative that I found was use #ResResource(exported=false).
This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value
I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:
if (associations.isLinkableAssociation(persistentProperty)) {
if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
return Collections.emptyList();
}
}
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortByLinkableAssociation {
}
At project mark association as #SortByLinkableAssociation:
#ManyToOne
#SortByLinkableAssociation
private Name name;
Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.
Please see https://stackoverflow.com/a/66135148/6673169 for possible workaround/hack, when we wanted sorting by linked entity.

Spring JPA nested Exception: detached entity passed to persist

I tried almost all what I could find here in SO, and another sites tutorials about creating an One to One Relationship with Hibernate.
So, I have two models, here are the last modifications, like for example the #MapsId annotation I also removed in previous test.
Usuario:
#Entity
#Table(name="usuarios")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Usuario {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="usuarios_id_seq")
#SequenceGenerator(name="usuarios_id_seq", sequenceName="usuarios_id_seq", allocationSize=1)
#Column(name="id")
private Long id;
#ManyToOne
#JoinTable(name="roles_usuarios", joinColumns={#JoinColumn(name="usuarios_id", referencedColumnName="id")}, inverseJoinColumns={#JoinColumn(name="roles_id", referencedColumnName="id")})
private Rol rol;
#OneToOne(cascade = CascadeType.ALL, mappedBy="usuario")
private Cliente cliente;
Cliente:
#Entity
#Table(name="clientes")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Cliente {
#Id
//#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="clientes_id_seq")
//#SequenceGenerator(name="clientes_id_seq", sequenceName="clientes_id_seq", allocationSize=1)
//#Column(name="id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="usuario_id", referencedColumnName="id")
#MapsId
private Usuario usuario;
Cliente Controller:
#PostMapping("/")
public ResponseEntity<Void> postCliente(#RequestBody Cliente cliente, UriComponentsBuilder ucBuilder) {
if( clienteService.isClienteExist(cliente) ){
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}
clienteService.save(cliente);
HttpHeaders headers = new HttpHeaders();
headers.setLocation( ucBuilder.path("/{id}").buildAndExpand(cliente.getId()).toUri() );
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
Cliente Service:
#Override
public Cliente save(Cliente cliente) {
Cliente clt = new Cliente();
clt.setUsuario(cliente.getUsuario());
clt.setRazonSocial(cliente.getRazonSocial());
clt.setRfc(cliente.getRfc());
clt.setDireccion(cliente.getDireccion());
clt.setEmail(cliente.getEmail());
clt.setTelefono(cliente.getTelefono());
clt.setContacto(cliente.getContacto());
clt.setTelContacto(cliente.getTelContacto());
clt.setEmailContacto(cliente.getEmailContacto());
return clienteRepository.save(clt);
}
If you notice I also have a many to one relationship with a Rol table which works fine, but when I pass information in the OneToOne which I pass it as a JSON it produces: detached entity passed to persist: com.swargos.entities.Usuario
IDK if I'm missing some annotations, or is that the database is created when running the spring application.
I'm providing a somewhat qualified guess, since you didn't include code that shows how you call persist.
The error means that the Usuario instance you are passing to persist() already has a primary key, but it is not a managed entity of that persistence context, see here for Entity Object lifecycle
My guess is that the Usuario instance was loaded by another EntityManager, then json-serialized to the front-end, and then posted back to the backend, and you need to set it on a Cliente (Since you have cascade in both directions it may also be the Cliente being set on the Usuario). Every time an entity has been loaded in one Persistence Context, and you want to save it in another you must either call em.merge() or you must call em.find() to load it into it (and then set the changes).
JPA is not magic, the life-cycle of the Entities and the Persistence Context which manage them is well defined, and unless the developer understands how these mechanisms work, a lot of time will be wasted trying to work against the framework.
Also #MapsId should only be used if Cliente used an #EmbeddedId for it primary key, which does not seem to be the case.

Get entity property with Spring JPA

I'm using Spring JPA in my DAO layer. I have an entity Projet having inside an entity property Client:
Project.java
#Entity
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int projetId;
private String libelle;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="client_id")
private Client client;
// ... constructors, getters & setters
}
Client.java
#Entity
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int clientId;
private String denomination;
// ... constructors, getters & setters
}
in my DAO interface I have the following specifications:
ProjetDao.java
#Repository
#Transactional
public interface ProjetDao extends CrudRepository<Projet, Integer> {
#Transactional
public Projet findByLibelle(String libelle);
#Transactional
public Projet findByProjetId(int projetId);
}
My question is: How can I specify in my DAO interface a method that will return all clients distinct in List<Client>?
From the documentation and JIRA:
List<Project> findAllDistinctBy();
The query builder mechanism built into Spring Data repository infrastructure is useful for building constraining queries over entities of the repository. The mechanism strips the prefixes find…By, read…By, query…By, count…By, and get…By from the method and starts parsing the rest of it. The introducing clause can contain further expressions such as a Distinct to set a distinct flag on the query to be created. However, the first By acts as delimiter to indicate the start of the actual criteria. At a very basic level you can define conditions on entity properties and concatenate them with And and Or.
You are dealing with a one-to-one relationship, in this case I guess the list that you need is not really related to specific project, you just want a distinct list of clients.
You will need to create another repository (ClientRepository) for the Client entity and add a findAllDistinct method in this repository.

How can I reload my hibernate dependent objects before de validation

I have an Spring controller with code like:
#RequestMapping("save")
public String save(#ModelAttribute #Valid Form form, BindingResult result){
if( result.hasErrors()){
[...]
My form contains a list of hibernate objects. All have their properties setted. I create an edit HTML form and in the controller I find that all the objects on the ManyToOne relationships is lost. I only have the ID. I could reload data from the database but it is too late for the validation casued by the #valid annotation.
public class Form{
#Valid
#NotNull
private List<Item> item;
#NotNull
private Foo foo;
[...]
And Item
#Entity
#Table(name = "item")
#XmlRootElement
public class Item{
#ManyToOne()
#JoinColumn(name = "dependent", referencedColumnName = "id", nullable = false)
#NotNull
private Dependent dependent;
#NotNull
private Currency currency;
How could I set the Dependent and Currency fields before the validation? Is there any alternative to reload data from the database?
(Disclaimer some names have been changes to protect the inocent)
If you are using Spring-Data-JPA you can register DomainClassConverter to do this work for you. In another case you may write such converter by yourself.
I found one way to do it:
Add to the controller a reference to SmartValidator.
#Autowired private SmartValidator validator;
Remove the #valid annotation. Reload all ManyToOne tables and call manually the validator.
#RequestMapping("save")
public String save(#ModelAttribute Form form, BindingResult result){
for(Item item : form.getItems()){
item.setDependant( myDAO.reload(item.getDependent()));
}
validator.validate(form, result);
if( result.hasErrors()){
[...]

Resources