There are a lot of articles here and all over the web, but these all target different Objectify versions and seem not to work for one or the other reason.
I have an entity, which references another entity (e.g. an Account entity references a User entity):
#Cache
#Entity
public final class Account {
#Id Long id;
#Index private Ref<User> user;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user.get();
}
public void setUser(User user) {
this.user = Ref.create(user);
}
}
I am trying to do this:
From the client, GET the account entity over REST/Google Cloud Endpoints.
Modify the resource.
UPDATE it on the server.
As discussed here Objectify loads object behind Ref<?> even when #Load is not specified above code always returns the referenced user as well, whích I don't want.
One option would be, as #svpino suggested, "Make your #ApiMethod return a different Account object without the user property (thus avoiding fetching the user if you don't need it)." This works as long as I don't want to UPDATE the resource. If I need to UPDATE, the Key/Ref needs to be preserved (even though I don't need it on the client).
One possible approach that I can see would be using Key instead of Ref and rendering a web-safe string, then recreating the user during UPDATE.
private Key<User> user;
public String getUser() {
return user.toString();
}
public void setUser(String user) {
this.user = Key.create(user);
}
The string looks like "Key(User(5723348596162560))", but it seems not to be reconstituted (at least I get an exception here, haven't tracked it down yet).
Another approach would be writing an #ApiTransformer, which did not solve the problem either.
Jeff #StickFigure posted several times during the last years and the issue still seems not to be solved.
What's the current state with Objectify 5.0.2 and what's the recommendation for preserving the key between roundtrips, when the key is not needed on the client?
You need to annotate the property that you want to omit with #ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
Google documentation says the following about the #ApiResourceProperty:
#ApiResourceProperty provides provides more control over how resource
properties are exposed in the API. You can use it on a property getter
or setter to omit the property from an API resource. You can also use
it on the field itself, if the field is private, to expose it in the
API. You can also use this annotation to change the name of a property
in an API resource.
I encourage you to read more by visiting this link
https://developers.google.com/appengine/docs/java/endpoints/annotations#apiresourceproperty
So in your case your class should look like this after the modification.
#Cache
#Entity
public final class Account
{
#Id Long id;
#Index private Ref<User> user;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public User getUser() {
return user.get();
}
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public void setUser(User user) {
this.user = Ref.create(user);
}
}
The following code serializes an entity object to a web-safe string so it can be transferred over REST. When the entity is sent back to the server, the Ref<> is reconstituted. This way a server-side reference is not lost while the object does a round-trip to the client. This way referenced objects are not transferred to the client and back, but can be "worked" as Ref<> on the client.
#Index private Ref<User> user;
// for serialization
public String getUser() {
return user.getKey().getString(); // .toWebSafeString() will be added in future version of objectify and .toWebSafeString() will do the same as .getString()
}
public void setUser(String webSafeString) {
Key<User> key = Key.create(webSafeString);
this.user = Ref.create(key);
}
Two separate functions (not named well, I admit) are there for loading the actual object on the server and for creating the reference in the first place:
// for load and create reference
public User loadUser() {
return user.get();
}
public void referenceUser(User user) {
this.user = Ref.create(user);
}
I hope this solves the problem for everybody. This did not yet go through thorough testing, so comments are still welcome.
I have run a test to compare between using a Key<> and a Ref<> and to me it looks like even with Ref<> the entity is only reconstituted when loadEntity()/.get() is called. So Ref<> if probably better as #Load annotations will work. Maybe the objectify guys can confirm this.
You can create a class that extends Ref<User> and use an #ApiTransformer to transfer that class between backend and client
#ApiTransformer(UserRefTransformer.class)
public class UserRef extends LiveRef<User>
{
}
public class UserRefTransformer implements Transformer<UserRef, User>
{
// Your transformation code goes here
}
Related
So I have looked at various tutorials about JPA with Spring Data and this has been done different on many occasions and I am no quite sure what the correct approach is.
Assume there is the follwing entity:
package stackoverflowTest.dao;
import javax.persistence.*;
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
public Customer(String name) {
this.name = name;
}
public Customer() {
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
We also have a DTO which is retrieved in the service layer and then handed to the controller/client side.
package stackoverflowTest.dto;
public class CustomerDto {
private long id;
private String name;
public CustomerDto(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
So now assume the Customer wants to change his name in the webui - then there will be some controller action, where there will be the updated DTO with the old ID and the new name.
Now I have to save this updated DTO to the database.
Unluckily currently there is no way to update an existing customer (except than deleting the entry in the DB and creating a new Cusomter with a new auto-generated id)
However as this is not feasible (especially considering such an entity could have hundreds of relations potentially) - so there come 2 straight forward solutions to my mind:
make a setter for the id in the Customer class - and thus allow setting of the id and then save the Customer object via the corresponding repository.
or
add the id field to the constructor and whenever you want to update a customer you always create a new object with the old id, but the new values for the other fields (in this case only the name)
So my question is wether there is a general rule how to do this?
Any maybe what the drawbacks of the 2 methods I explained are?
Even better then #Tanjim Rahman answer you can using Spring Data JPA use the method T getOne(ID id)
Customer customerToUpdate = customerRepository.getOne(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);
Is's better because getOne(ID id) gets you only a reference (proxy) object and does not fetch it from the DB. On this reference you can set what you want and on save() it will do just an SQL UPDATE statement like you expect it. In comparsion when you call find() like in #Tanjim Rahmans answer spring data JPA will do an SQL SELECT to physically fetch the entity from the DB, which you dont need, when you are just updating.
In Spring Data you simply define an update query if you have the ID
#Repository
public interface CustomerRepository extends JpaRepository<Customer , Long> {
#Query("update Customer c set c.name = :name WHERE c.id = :customerId")
void setCustomerName(#Param("customerId") Long id, #Param("name") String name);
}
Some solutions claim to use Spring data and do JPA oldschool (even in a manner with lost updates) instead.
Simple JPA update..
Customer customer = em.find(id, Customer.class); //Consider em as JPA EntityManager
customer.setName(customerDto.getName);
em.merge(customer);
This is more an object initialzation question more than a jpa question, both methods work and you can have both of them at the same time , usually if the data member value is ready before the instantiation you use the constructor parameters, if this value could be updated after the instantiation you should have a setter.
If you need to work with DTOs rather than entities directly then you should retrieve the existing Customer instance and map the updated fields from the DTO to that.
Customer entity = //load from DB
//map fields from DTO to entity
So now assume the Customer wants to change his name in the webui -
then there will be some controller action, where there will be the
updated DTO with the old ID and the new name.
Normally, you have the following workflow:
User requests his data from server and obtains them in UI;
User corrects his data and sends it back to server with already present ID;
On server you obtain DTO with updated data by user, find it in DB by ID (otherwise throw exception) and transform DTO -> Entity with all given data, foreign keys, etc...
Then you just merge it, or if using Spring Data invoke save(), which in turn will merge it (see this thread);
P.S. This operation will inevitably issue 2 queries: select and update. Again, 2 queries, even if you wanna update a single field. However, if you utilize Hibernate's proprietary #DynamicUpdate annotation on top of entity class, it will help you not to include into update statement all the fields, but only those that actually changed.
P.S. If you do not wanna pay for first select statement and prefer to use Spring Data's #Modifying query, be prepared to lose L2C cache region related to modifiable entity; even worse situation with native update queries (see this thread) and also of course be prepared to write those queries manually, test them and support them in the future.
I have encountered this issue!
Luckily, I determine 2 ways and understand some things but the rest is not clear.
Hope someone discuss or support if you know.
Use RepositoryExtendJPA.save(entity). Example:
List<Person> person = this.PersonRepository.findById(0)
person.setName("Neo");
This.PersonReository.save(person);
this block code updated new name for record which has id = 0;
Use #Transactional from javax or spring framework. Let put #Transactional upon your class or specified function, both are ok. I read at somewhere that this annotation do a "commit" action at the end your function flow. So, every things you modified at entity would be updated to database.
There is a method in JpaRepository
getOne
It is deprecated at the moment in favor of
getById
So correct approach would be
Customer customerToUpdate = customerRepository.getById(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);
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...
}
What I would like to realize is the following:
I have a dashboard class and a user class.
In my (Java EE project) Java code I would like to get all dashboards, to which the user has been subscribed.
The database contains a table (dashboard_users), with the following fields: idUser, idDashboard, isDefault en ID.
There should also be a Java POJO of the joined tabled.
My question:
How should the JPA M-M connection between these three classes look like (Dashboard.java/User.java/UserDashboard.java)?
I followed a lot of tutorials and examples, but for some reason there are always errors or other problems. It would be very welcome if someone could give an example, so I can see what I am doing wrong.
Thank you
Given the extra attribute on the association table you are going to need to model it (via a UserDashboard.java class as you asked). Quite unfortunate, as it adds a significant amount of work to your model layer.
If you find you do not need the extra attribute after all then I would model User with a set of Dashboards, linked directly via a #JoinTable.
One way you could do this would be to see the relationship between User and Dashboard as a map in which the Dashboard is a key, there being an entry for every Dashboard associated with the User, and the value is a flag indicating whether that Dashboard is the default for that User. I admit this is a bit forced; it's an odd way to look at the relationship, perhaps even suspect as has been charged.
But the advantage of this view is that it lets you map the living daylights out of everything like this:
#Entity
public class Dashboard {
#Id
private int id;
private String name;
public Dashboard(int id, String name) {
this.id = id;
this.name = name;
}
protected Dashboard() {}
}
#Entity
public class User {
#Id
private int id;
private String name;
#ElementCollection
private Map<Dashboard, Boolean> dashboards;
public User(int id, String name) {
this.id = id;
this.name = name;
this.dashboards = new HashMap<Dashboard, Boolean>();
}
protected User() {}
// disclaimer: the following 'business logic' is not necessarily of the finest quality
public Set<Dashboard> getDashboards() {
return dashboards.keySet();
}
public Dashboard getDefaultDashboard() {
for (Entry<Dashboard, Boolean> dashboard : dashboards.entrySet()) {
if (dashboard.getValue()) {
return dashboard.getKey();
}
}
return null;
}
public void addDashboard(Dashboard dashboard) {
dashboards.put(dashboard, false);
}
public void setDefaultDashboard(Dashboard newDefaultDashboard) {
Dashboard oldDefaultDashboard = getDefaultDashboard();
if (oldDefaultDashboard != null) {
dashboards.put(oldDefaultDashboard, false);
}
dashboards.put(newDefaultDashboard, true);
}
}
This maps a table structure which looks like this Hibernate-generated SQL, which i think is roughly what you want. The generated names on the User_dashboards table are pretty shoddy; you could customise them quite easily with some annotations or some XML. Personally, i like to keep all the filthy details of the actual mapping between the objects and the database in an orm.xml; here's what you'd need to add to use more sensible names:
<entity class="User">
<attributes>
<element-collection name="dashboards">
<map-key-join-column name="Dashboard_id" />
<column name="is_default" />
</element-collection>
</attributes>
</entity>
My client application is a standalone java application using Netbeans RCP. The client application connect to a remote EJB mounted on a Glassfish server.
On the server side, i have a business method (i made for testing) that's supposed to take an instance of an entity (this entity is not persisted or managed yet), persist it (using the persit of the EntityManager).
Here is how this method looks like:
#Override
public TestLinkEntity test(TestLinkEntity c) {
em.persist(c);
return c;
}
Called from the client side like this:
TestLinkEntity c = remote.test(new TestLinkEntity());
Here is the TestLinkEntity declaration:
#Entity
public class TestLinkEntity implements AltEntity, Serializable {
private Set<TestAppEntity> links = new HashSet<TestAppEntity>();
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Override
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#OneToMany(mappedBy="links")
public Set<TestAppEntity> getLinks() {
return links;
}
}
And now the TestAppEntity used in the one to many relationship.
#Entity
public class TestAppEntity implements Serializable, AltEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String test;
public TestAppEntity() {
}
#Override
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
The problem i'm facing is when i try to call the business method from the client (as shown above) i get a huge and quite unreadable exception on client side and only on client side. No exception logged on the Glassfish logs and the entity (TestLinkEntity)and potential links (TestAppEntity) are stored in the data base.
I pasted the exception here.
Here are few things i have noticed.
The exception only happen when i try to return a managed entity. If i replace the em.persist() by em.merge and do not return the new entity returned by merge for example, the exception will never be raised. e.g do something like:
#Override
public TestLinkEntity test(TestLinkEntity c) {
em.merge(c);
return c;
}
The exception only happen when the entity returned contains the one to many relationship. e.g something like the following code snippet will not raise an exception:
#Override
public TestAppEntity test(TestAppEntity c) {
em.persist(c);
return c;
}
TestAppEntity does not contain any relationship.
Things i'm sure:
TestAppEntity and TestLinkEntity are the same on both client and server sides.
EDIT:
Due to the answer of #James, i'm now wondering what is the difference between the package javax.persistence in eclispelink.jar and in javaee.jar. Should i include both?
Including both cause troubles (maybe due to the fact that package name in both jars are the same).
error: in class file javax/persistence/NamedQuery.class(javax/persistence:NamedQuery.class): unknown enum constant javax.persistence.LockModeType.NONE
Note: Attempting to workaround javac bug #6512707
warning: No processor claimed any of these annotations: [javax.ejb.Remote]
error: in class file j javax/persistence/NamedQuery.class(javax/persistence:NamedQuery.class): unknown enum constant javax.persistence.LockModeType.NONE
It seems to be some bug in the CORBA serialization you are using.
My guess is you don't have the eclipseLink.jar on your client, but you need it. As objects read from the database will contains special LAZY collections instances.
I am not sure what the best practice is for dealing with collection/lookup tables/in RequestFactory.
For example if I have following two Domain objects:
#Entity
public class Experiment {
private Long id;
private String name;
#ManyToOne(cascade={CascadeType.PERSIST,CascadeType.MERGE})
private UnitOfMeasure unitOfMeasure;
public Experiment() { }
public String getName() {
return name;
}
public Long getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
public UnitOfMeasure getUnitOfMeasure() {
return unitOfMeasure;
}
public void setUnitOfMeasure(UnitOfMeasure unitOfMeasure) {
this.unitOfMeasure = unitOfMeasure;
}
}
#Entity
public class UnitOfMeasure {
private Long id;
private String unit_type;
public UnitOfMeasure() { }
public String getUnitType() {
return unit_type;
}
public Long getId() {
return id;
}
public void setUnitType(String unitType) {
this.unit_type = unitType;
}
}
This is a normal unidirectional 1:n realtionship between Experiment and UnitOfMeasure using a ForeignKey in the Experiment table.
I have a limited amount of different UnitOfMeasure instances which usually don't change.
The web-app provides a view where the user can change some properties of the Experiment instance. The view uses the Editor framework. For changing the UnitOfMeasure of a specific Experiment I use a ValueListBox and render the unit_type property.
Because the list of available UnitOfMeasure instances is static I use AutoBeanFactory to create a json string which I put into the HTML host page and during application start I parse it (same thing for all other collection like table values) and store them in a Singleton class instance (AppData) which I pass to `setAcceptableValues``.
Currently I derive UnitOfMeasureProxy from EntityProxy but in order to decode/encode it with AutoBeanFactory I have to annotate the Factory with EntityProxyCategory. I somehow suspect that a ValueProxy would be a better fit.
However with a ValueProxy when I change the UnitOfMeasure of a specific Experiment the entire ValueProxy instance is transmitted over the wire.
From a database point of view however only changing the value for the foreignkey in the Experiment table is required.
So what is the best practice (ValueProxy vs EntityProxy) for collection like tables and child values respectively?
In many cases, references to other entities are best modelled using their IDs rather than the EntityProxys themselves (it's debatable, but I think it's also true for server-side code, or actually any code that crosses unit-of-work boundaries –JPA EntityManager lifetime, Hibernate session, etc.–)
BTW, the proper way to serialize RequestFactory proxies is to use a ProxySerializer.
Make sure you use GWT 2.5.0-rc1 though if you have lists of ValueProxys (see issue 6961)