as the title indicate i have a native SQL query in my repository like that
#Repository
public interface BesoinRepository extends CrudRepository<Statistic, Long>{
#Query(value="SELECT etat_besoin AS 'state',COUNT(DISTINCT id) AS 'number' FROM besoin WHERE YEAR(date_creation)=:year GROUP BY etat_besoin ",nativeQuery=true)
List<Object> getStatistic(#Param("year") int year);
}
class statistic
package fr.solinum.management.model;
public class Statistic {
private String state;
private int number;
public String getState() {
return state;
}
public int getNumber() {
return number;
}
}
and this is working ok but i want the return type of getStatistic to be List<Statistic> and without creating the table Statistic in my database since i dont need it. in other word i want only to read from the database and return the result as a class. so what are the changes in the model and in the repository or in the controller or what is the optimal approach for my problem? note that i modified my model class as following to solve that but i dont think that this is the optimal or the good approach.
package fr.solinum.management.model;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "besoin")
public class Statistic {
#Id
private int id;
private String state;
private int number;
public String getState() {
return state;
}
public int getNumber() {
return number;
}
}
I can think of three approaches:
Use a Database View.
With this, you can have your entity as you want mapped as any other table to the View. You can use a Repository with no problems and you don't create a different table.
Map your List in a class and access this data from a service/DAO.
This is more labor intensive and might not "fit" with the rest of your code. Instead of using your repository directly, you would use another class. This class would do the querying of Objects and map them before delivering them to the controller or service where they are being requested. It's like doing JPA's work, really.
Take the following code as example:
#Component
public class StatisticDAO {
#Autowired
private BesoinRepository besoinRepository;
public List<Statistic> getStatistic(int year) {
List<Statistic> result = new ArrayList<>();
List<Object[]> temp = besoinRepository.getStatistic(year);
temp.stream().forEach(data -> result.add(build(data)));
return result;
}
private Statistic build(Object[] data) {
return new Statistic(String.valueOf(data[0]), (Integer)data[1]);
}
}
You'll need to change the type of the list to Object[] and check the order in which order JPA is returning the attributes.
Use interface-based projections.
I'd recommend approaches 1 and 3, but is up to you and whatever suits you better.
I am using #RepositoryRestResource facility,but I would like to do a calculation (depending on external service) over a transient field each time the user executes a read operation GET, find etc..
If only there was an AfterGet/AfterFind event at list I could handle the modification by extending AbstractRepositoryEventListener
Any clean suggestion?
I found the way through #Alan Hay suggestion.
#Entity
#EntityListeners(TransientFieldResolver.class)
public class Entity {
private Long id;
private String transientField;
}
#Component
public static class TransientFieldResolver {
private static ExternalService externalService;
#Autowired
public void setExternalService(ExternalService externalService) {
TransientFieldResolver.externalService = externalService;
}
#PostLoad
public void onPostLoad(final Entity entity) {
if (Objects.isNull(entity.getTransientField())) {
TransientFieldResolver.externalService.fillTransientField(entity);
}
}
}
I'm using Spring Data REST to build my application. It's been working very well so far, but I'd like to add some customizations to returned entity while still keeping the automatically generated links.
I'd like to do something like this:
#RepositoryRestController
public class SomeController {
#GetMapping("/entity/{id}")
public SomeEntity getEntity(#PathVariable int id)
SomeEntity entity = SpringDataREST.findById(id); //-> is there a way to do this?
Link randomLink = generateRandomLink();
entity.addLink(randomLink);
//do other stuff with entity
return entity;
}
}
Where SomeEntity class extends Spring HATEOAS ResourceSupport.
If you are using Spring Data REST you can use RepositoryEntityLinks to programmatically create links:
#Component
public class MyBean {
private final RepositoryEntityLinks entityLinks;
public MyBean(RepositoryEntityLinks entityLinks) {
this.entityLinks = entityLinks;
}
public Link someMethod(MyEntity entity) {
//...
return entityLinks.linkToSingleResource(entity)
}
}
Note - to use linkToSingleResource method, the MyEntity must implement Identifiable interface. Instead you can use method linkForSingleResource:
return entityLinks.linkForSingleResource(MyEntity.class, entity.getId())
I have a JPA Project (Eclipse Link), works fine but I want to persist a class that is not Entity(Or Not entity in the same Persistence Context), currently I persist the reference id, and after that I do the call to retrieve the Object. I need know what is the best way to do that.. I do not want add code in the bean as listener event, because I want have a clean bean(constructos,properties, setters and getters without annotations),
My Idea is to extend the PersistenceContext(but, I do not know how to do it) adding a filter and identify the class to persist and doing something to replace the persistence of the class not mapped.
Any ideas or my question is out of place?
This is a Simple Example..
#Entity
public class Customer{
#Column
Integer id;
#Column
/*transient?*/
CustomerInfo customerInfo
/*setters and getters*/
}
/*this class maybe not be Entity.. Maybe be a Web Service Response Bean*/
public class CustomerInfo{
private String firstName;
private String lastName;
private BigDecimal balance;
/*setters and getters*/
}
Following my comments: create an embeddable CustomerInfoKey with the essential information.
Two solutions then:
Inheritance:
Have CustomerInfo inherits CustomerInfoKey
Use setCustomerInfoKey(customerInfo) with customerInfo a CustomerInfo.
Composition and delegation:
Have a field CustomerInfoKey key in CustomerInfo
Delegate the getter/setter of CustomerInfoKey in CustomerInfo:
public Foobar getFoobar() {return key.getFoobar()}
public void setFoobar(Foobar foobar) {key.setFoobar(key);}
Have a method getKey() and use it to persist data; you can even create a setter taking a CustomerInfo in Customer and doing the appropriate stuff.
I don't know how JPA implementations behave when it encounters partial mapping like solution #1. But it should work.
As proposed by NoDataFound in the comment, if you do not want to add an Id, an Embeddable/Embedded tandem could be the solution: because of the Id problem, you should have the data in the same table (it is possible to keep different classes). You have the doc in the Java EE tutorial. If you don't want to change the code, you could use the XML for object/relational mapping. In the wikibook about JPA you have an XML sample.
To resolve this, I am creating a EntityListener:
public interface JREntityListener<T> {
public Class getTarget();
public void postUpdate(T t) throws Exception;
public void postCreate(T t) throws Exception;
public void preMerge(T t) throws Exception;
public void postMerge(T t) throws Exception;
public void prePersist(T t) throws Exception;
public void postPersist(T t) throws Exception;
}
I am Created a Class to Catch the events of a Entity
public class JRDescriptorEventListener<T> implements DescriptorEventListener{
//implements all methods of DescriptorEventListener
//i am Show only One to Example
#Override
public void postClone(DescriptorEvent descriptorEvent) {
// descriptorEvent.getObject();
try {
logger.info("postClone");
t.postUpdate((T) descriptorEvent.getObject());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
I am created a Binding EntityListener with PersistenceContext (Thanks jhadley by injecting a spring dependency into a JPA EntityListener):
public void addListener(JREntityListener t) {
JpaEntityManager entityManager = null;
try {
// Create an entity manager for use in this function
entityManager = (JpaEntityManager) entityManagerFactory.createEntityManager();
// Use the entity manager to get a ClassDescriptor for the Entity class
ClassDescriptor desc =
entityManager.getSession().getClassDescriptor(t.getTarget());
JRDescriptorEventListener jrDescriptorEventListener = new JRDescriptorEventListener(t);
desc.getEventManager().addListener(jrDescriptorEventListener);
logger.info("Entity Listener for " + t.getTarget().getCanonicalName() + " is added");
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (entityManager != null) {
// Cleanup the entity manager
entityManager.close();
}
}
}
Now I am implements the Listener addind Listener
javoraiPersistenceBase.addListener(myEntityListener);
public class MyEntityListener implements JavoraiEntityListener<Customer> {
#Autowired
CustomerSvc customerSvc;
#Override
public Class getTarget() {
return Customer.class;
}
#Override
public void postUpdate(Customer customer) throws Exception {
CustomerInfo customerInfo = globalDataSvc.findCustomerInfoById(customer.getCustomerInfo().getId());
customer.setCustomerInfo(customerInfo);
}
}
I'm trying to get the old entity in a #HandleBeforeSave event.
#Component
#RepositoryEventHandler(Customer.class)
public class CustomerEventHandler {
private CustomerRepository customerRepository;
#Autowired
public CustomerEventHandler(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#HandleBeforeSave
public void handleBeforeSave(Customer customer) {
System.out.println("handleBeforeSave :: customer.id = " + customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
Customer old = customerRepository.findOne(customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
System.out.println("handleBeforeSave :: old customer.name = " + old.getName());
}
}
In the event I try to get the old entity using the findOne method but this return the new event. Probably because of Hibernate/Repository caching in the current session.
Is there a way to get the old entity?
I need this to determine if a given property is changed or not. In case the property is changes I need to perform some action.
If using Hibernate, you could simply detach the new version from the session and load the old version:
#RepositoryEventHandler
#Component
public class PersonEventHandler {
#PersistenceContext
private EntityManager entityManager;
#HandleBeforeSave
public void handlePersonSave(Person newPerson) {
entityManager.detach(newPerson);
Person currentPerson = personRepository.findOne(newPerson.getId());
if (!newPerson.getName().equals(currentPerson.getName)) {
//react on name change
}
}
}
Thanks Marcel Overdijk, for creating the ticket -> https://jira.spring.io/browse/DATAREST-373
I saw the other workarounds for this issue and want to contribute my workaround as well, cause I think it´s quite simple to implement.
First, set a transient flag in your domain model (e.g. Account):
#JsonIgnore
#Transient
private boolean passwordReset;
#JsonIgnore
public boolean isPasswordReset() {
return passwordReset;
}
#JsonProperty
public void setPasswordReset(boolean passwordReset) {
this.passwordReset = passwordReset;
}
Second, check the flag in your EventHandler:
#Component
#RepositoryEventHandler
public class AccountRepositoryEventHandler {
#Resource
private PasswordEncoder passwordEncoder;
#HandleBeforeSave
public void onResetPassword(Account account) {
if (account.isPasswordReset()) {
account.setPassword(encodePassword(account.getPassword()));
}
}
private String encodePassword(String plainPassword) {
return passwordEncoder.encode(plainPassword);
}
}
Note: For this solution you need to send an additionally resetPassword = true parameter!
For me, I´m sending a HTTP PATCH to my resource endpoint with the following request payload:
{
"passwordReset": true,
"password": "someNewSecurePassword"
}
You're currently using a spring-data abstraction over hibernate.
If the find returns the new values, spring-data has apparently already attached the object to the hibernate session.
I think you have three options:
Fetch the object in a separate session/transaction before the current season is flushed. This is awkward and requires very subtle configuration.
Fetch the previous version before spring attached the new object. This is quite doable. You could do it in the service layer before handing the object to the repository. You can, however not save an object too an hibernate session when another infect with the same type and id it's known to our. Use merge or evict in that case.
Use a lower level hibernate interceptor as described here. As you see the onFlushDirty has both values as parameters. Take note though, that hibernate normally does not query for previous state of you simply save an already persisted entity. In stead a simple update is issued in the db (no select). You can force the select by configuring select-before-update on your entity.
Create following and extend your entities with it:
#MappedSuperclass
public class OEntity<T> {
#Transient
T originalObj;
#Transient
public T getOriginalObj(){
return this.originalObj;
}
#PostLoad
public void onLoad(){
ObjectMapper mapper = new ObjectMapper();
try {
String serialized = mapper.writeValueAsString(this);
this.originalObj = (T) mapper.readValue(serialized, this.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
I had exactly this need and resolved adding a transient field to the entity to keep the old value, and modifying the setter method to store the previous value in the transient field.
Since json deserializing uses setter methods to map rest data to the entity, in the RepositoryEventHandler I will check the transient field to track changes.
#Column(name="STATUS")
private FundStatus status;
#JsonIgnore
private transient FundStatus oldStatus;
public FundStatus getStatus() {
return status;
}
public FundStatus getOldStatus() {
return this.oldStatus;
}
public void setStatus(FundStatus status) {
this.oldStatus = this.status;
this.status = status;
}
from application logs:
2017-11-23 10:17:56,715 CompartmentRepositoryEventHandler - beforeSave begin
CompartmentEntity [status=ACTIVE, oldStatus=CREATED]
Spring Data Rest can't and likely won't ever be able to do this due to where the events are fired from. If you're using Hibernate you can use Hibernate spi events and event listeners to do this, you can implement PreUpdateEventListener and then register your class with the EventListenerRegistry in the sessionFactory. I created a small spring library to handle all of the setup for you.
https://github.com/teastman/spring-data-hibernate-event
If you're using Spring Boot, the gist of it works like this, add the dependency:
<dependency>
<groupId>io.github.teastman</groupId>
<artifactId>spring-data-hibernate-event</artifactId>
<version>1.0.0</version>
</dependency>
Then add the annotation #HibernateEventListener to any method where the first parameter is the entity you want to listen to, and the second parameter is the Hibernate event that you want to listen for. I've also added the static util function getPropertyIndex to more easily get access to the specific property you want to check, but you can also just look at the raw Hibernate event.
#HibernateEventListener
public void onUpdate(MyEntity entity, PreUpdateEvent event) {
int index = getPropertyIndex(event, "name");
if (event.getOldState()[index] != event.getState()[index]) {
// The name changed.
}
}
Just another solution using model:
public class Customer {
#JsonIgnore
private String name;
#JsonIgnore
#Transient
private String newName;
public void setName(String name){
this.name = name;
}
#JsonProperty("name")
public void setNewName(String newName){
this.newName = newName;
}
#JsonProperty
public void getName(String name){
return name;
}
public void getNewName(String newName){
return newName;
}
}
Alternative to consider. Might be reasonable if you need some special handling for this use-case then treat it separately. Do not allow direct property writing on the object. Create a separate endpoint with a custom controller to rename customer.
Example request:
POST /customers/{id}/identity
{
"name": "New name"
}
I had the same problem, but I wanted the old entity available in the save(S entity) method of a REST repository implementation (Spring Data REST).
What I did was to load the old entity using a 'clean' entity manager from which I create my QueryDSL query:
#Override
#Transactional
public <S extends Entity> S save(S entity) {
EntityManager cleanEM = entityManager.getEntityManagerFactory().createEntityManager();
JPAQuery<AccessControl> query = new JPAQuery<AccessControl>(cleanEM);
//here do what I need with the query which can retrieve all old values
cleanEM.close();
return super.save(entity);
}
The following worked for me. Without starting a new thread the hibernate session will provide the already updated version. Starting another thread is a way to have a separate JPA session.
#PreUpdate
Thread.start {
if (entity instanceof MyEntity) {
entity.previous = myEntityCrudRepository.findById(entity?.id).get()
}
}.join()
Just let me know if anybody would like more context.
Don't know if you're still after an answer, and this is probably a bit 'hacky', but you could form a query with an EntityManager and fetch the object that way ...
#Autowired
EntityManager em;
#HandleBeforeSave
public void handleBeforeSave(Customer obj) {
Query q = em.createQuery("SELECT a FROM CustomerRepository a WHERE a.id=" + obj.getId());
Customer ret = q.getSingleResult();
// ret should contain the 'before' object...
}