Grails difficultly in using Hibernate OneToMany model - java

In my new project I am trying to use Hibernate model class, here one user domain class having OneToMany relation userProfile like
class User {
//Some fields and getter setter
//Problematic thing
#javax.persistence.OneToMany(mappedBy = "User")
private Set<UserProfile> userProfiles;
//getter is like
public Set<userProfile> getProfile() {
// the logic
}
public void setProfile() {
// the logic
}
}
So when I try to access this field in grails criteria like
def criteria = User.createCriteria()
List<User> userList = criteria.list() {
userProfiles {
eq("id",1 as long)
}
}
I getting the error like No signature of method: UserService.userProfiles(). I think it might be because of different getter and setter name, 'cause for remaining OneToMany fields the criteria is working fine.
Is there any possible and standard way to address this issue.

This is a more common thing to do:
class User {
static hasMany = [userProfiles: UserProfile]
}
Methods like getUserProfiles(), setUserProfiles(), addToUserProfiles(UserProfile p) etc. are automatically generated. See http://grails.org/doc/latest/ref/Domain%20Classes/hasMany.html.
Then you could do something like this:
def userList = User.withCriteria {
userProfiles {
idEq 1
}
}
I hope that helps.

Related

How get Cassandra, Spring Boot to call field setters for an entity?

I have an entity that is grabbed from Cassandra by a repository. In it are some custom fields that I want set when certain managed fields are set by Spring/Cassandra.
But when I try to put the primary key signifier on the getter method (similar to JPA) it doesn't use the methods. How do I get it to call them when setting the fields?
#Table(name="entity")
public class MyEntity {
private String calculatedField;
private CompoundKey pk;
...elided...
#PrimaryKey
public void setPk(CompoundKey pk) {
this.pk = pk;
//do some calculations...
this.calculatedField = pk.getField() + val;
}
}
This always leaves calculatedField as null.
The AccessType annotation is exactly for that purpose.
Your entity should look like this:
#AccessType(Type.PROPERTY)
public class MyEntity {
//...
}

Spring MVC Transactional Best Practices for this

I have a controller method which retrieves an User, then I've got mapped their UserConfig, and then with that UserConfig I retrieve the MainBrands (lazy collection of UserConfiguration).
Let me clarify this:
User Entity:
#Entity
#Table(name = "app_user")
public class User extends BaseEntity {
private UserConfig userConfig;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
public UserConfig getUserConfig() {
return userConfig;
}
//more props..
}
UserConfig Entity:
#Entity
#Table(name = "user_config")
public class UserConfig extends BaseEntity {
private Set<MainBrand> mainBrands;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(...)
public Set<MainBrand> getMainBrands() {
return mainBrands;
}
//more props..
}
And my UserService:
public interface UserService {
public User getById(Long id);
}
So my question is about "best practices" of transactional annotations. I have read more than once, that put #Transactional at Controller level, is bad practice. But in this case I wanna do at Controller:
#RequestMapping(method = RequestMethod.GET, value = "/")
public ModelAndView getMainPage(Long userId) {
ModelAndView = new ModelAndView("/home");
//do stuff
User user = userService.getById(userId);
//some stuff with user
modelAndView.addObject("username", user.getUsername());
//...
List<String> brandsNames = new ArrayList<>();
for(MainBrand mainBrand : user.getUserConfig().getMainBrands()){
brandsNames.add(mainBrand.getName());
}
}
That will fail if don't put the #Transactional annotation at Controller level, because of LazyInitializationException.
So, that's the choices that I've thinked out:
1) With the user make a call to an "UserConfigService" (it's not created now) like userConfigService.getUserConfigByUserId(userId): that's make me think that if I already have the binding at User class, why I would call it again? And I am just creating a new service only for this method.
2) Put the #Transactional annotation at controller level: which makes another problem for my, but it doesn't care in this post.
3) Call the getUserConfig() & getUserConfig().getMainBrands() at UserService so then the collection get initialized: don't like because whenever I use the getById it will initialize the collection even if I do not need it.
So what it would be a good practice for this case? On internet there are always perfect and beautiful examples, but when we start to give some business logic to the project, it turns hard to have a clean code.
Thanks, and sorry for my english.
LazyInitializationException is not related to transactional , it is related to relationship between objects, if your object has a lazy relation,you must fetch your MainBrands objects in your userService.getById(userId) query method before you return your user.
Transactional annotation must be in service class, you can create as many service classes as you need.

How to get old entity value in #HandleBeforeSave event to determine if a property is changed or not?

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...
}

Update only changed entity fields and check for changes by other users in EJB

In an EJB-class i have two methods with remote interface:
Class MyBean {
public CustomerEntity getCustomer(String id) {......}
public void updateCustomer(CustomerEntity newValues, CustomerEntity oldValues) {......}
}
Customer Entity consist of some fields with getters and setters.
#Entity
public class Customer {
#ID private String id;
#Column private String name;
#Column private String phone;
// Getters and setters
.
.
}
The client app does:
Customer customer myBeanRemoteInterface.getCustomer("some id");
Customer oldCustomer = customer; //Save original customer data
displayCustomerFormAndAcceptChanges(customer);
myBeanRemoteInterface.updateCustomer(customer, oldCustomer);
The EJB updateCustomer should now update the customer on the server. To avoid write over any changes made to other fields by other users, only fields that the user has changed should be commited. Like the following:
public void updateCustomer(CustomerEntity newValues, CustomerEntity oldValues) {
Customer customer = entityManager.find(Customer.class, oldValues.getId());
if (!newValues.getName().equals(oldValues.getName()) { // Value updated
// If the value fetched by entityManager.find is different from what was originally fetched that indicates that the value has been updated by another user.
if (!customer.getName().equals(oldValues.getName()) throw new CustomerUpdatedByOtherUserException();
else customer.setName(newValues.getName());
}
// repeat the code block for every field in Customer class
entityManager.flush();
}
The problem now is that the code block in updateCustomer needs to be repeated once for every field in the Customer class. If new fields is inserted into the Customer class, also the EJB needs to be updated.
I need a solution that works without any need for updateing the EJB if more fields is added to Customer class.
Any suggestions?
Use Java Reflection:
Method[] methods = Customer.class.getDeclaredMethods();
Class<?>[] methodParams = null;
Object[] paramValue = new Object[1];
for (Method method : methods) {
if(method.getName().contains("set")) //This is for set methods.
{
methodParams = method.getParameterTypes();
if(methodParams[0].equals(String.class))
{
paramValue[0] = "some string"; // Assigning some value to method parameter
}
method.invoke(customer, paramValues); // customer is your object you are executing your methods on.
}
}
You should really consider adding a #Version annotated field to your Entity to have your JPA implementation take care of Optimistic Locking and then deal with situations where you try to update using "stale" data.
Otherwise you're jeopardizing your data-integrity.
Cheers //Lutz

JPA: Eclipselink does not persist bi-directional relationships in database

My domain model in my Java EE 6 application contains bi-directional relationships like the following:
#Entity
public class Users implements PrimaryKeyHolder<String>, Serializable {
#Id
private String username;
#ManyToMany(mappedBy= "users")
private List<Category> categories;
public List<Category> getCategories() {
if (categories == null) {
categories = new ArrayList<Category>();
}
return Collections.unmodifiableList(categories);
}
public void addCategory(Category category) {
if (categories == null) {
categories = new ArrayList<Category>();
}
categories.add(category);
if (!category.getUsers().contains(this)) {
category.addUser(this);
}
}
public void removeCategory(Category category) {
if (categories == null) {
categories = new ArrayList<Category>();
}
categories.remove(category);
if (category.getUsers().contains(this)) {
category.removeUser(this);
}
}
public void setCategories(Collection<Category> categories) {
if (this.categories == null) {
this.categories = new ArrayList<Category>();
}
for (Iterator<Category> it = this.categories.iterator(); it.hasNext();) {
Category category = it.next();
it.remove();
if (category.getUsers().contains(this)) {
category.removeUser(this);
}
}
for (Category category : categories) {
addCategory(category);
}
}
}
#Entity
public class Category implements PrimaryKeyHolder<Long>, Serializable {
#Id
private Long id;
#ManyToMany
private List<User> users;
public List<User> getUsers() {
if (users == null) {
users = new ArrayList<User>();
}
return Collections.unmodifiableList(users);
}
protected void addUser(User user) {
if (users == null) {
users = new ArrayList<User>();
}
users.add(user);
}
protected void removeUser(User user) {
if (users == null) {
users = new ArrayList<User>();
}
users.remove(user);
}
}
UPDATE: I added relationship management code. Relationships are only set on the user side, therefore, the add/remove methods are protected in the Categoriy class. I set the categories on the user via setCategories.
Eclipselink correctly generates a join table CATEGORY_USERS. However, it does not persist any information in it (it only caches the information). E.g. when I execute a find operation on the entity manager (e.g. a user), it returns the complete object graph (including the category relationship). But when I look at the tables, information are not updated (even though the transactions are committed). I also inserted a flush operation in my code, without success. Basic information (like String, Integer, etc. columns) gets correctly persisted and updated. After turning the log level to FINE, I can see that no SQL statements are executed for the relationships and the join table, respectively. But I do see SQL statements for uni-directional relationships.
My datamodel is covered by extensive unit tests, which all pass successfully. I basically do the same operation as in the container, commit the transaction, reload the entities from the db and check if the relationships are correctly set, which they are (I'm using the in-memory derby database for testing).
My app server is Glassfish v3.1-b17.
Any help is appreciated.
Thanks,
Theo
Ensure you are setting both sides of the relationship. The specification requires that the application sets both sides of the relationship as there is no relationship maintenance in JPA.
After endless hours of trying I finally got to a solution: I simply changed the owning side of the relationship, i.e. I put the mappedBy attribute to the category entity like this:
#ManyToMany(mappedBy= "categories")
private List<User> users;
The explanation for this can be found here
Four points:
1.- When you have an error, it's more simple find solution isolating them in an example (Or unit test) that reproduces the error. In your case, you could do an example with more simple getter and setter (for example, removing unmodifiableList use and other innecesary methods for testing actually issue).
2.- I advise you to use pojos for model, without any logic. So, remove logic from pojos.
3.- We are using eclipselink and we do not have problems persisting relations. So, it is more possible that error will be in your code.
4.- Test annoting relation with "cascade = javax.persistence.CascadeType.ALL"
Apology for my poor English :(

Categories

Resources