Spring Data Update Many To One Property - java

I have an problem with saving Many-To-One Field in Spring Data JPA.
Consider Entity User and Group as shortly described below :
#Entity
#Table(name = "Users")
public class User {
#Basic(optional = false)
#Column(name = "username")
private String username;
#JoinColumn(name = "group_id", referencedColumnName = "id")
#ManyToOne
private Group group;
}
#Entity
#Table(name = "groups")
public class Group {
#Basic(optional = false)
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "group",cascade=CascadeType.PERSIST)
private Collection<User> userCollection;
}
when i want to Update User Entity and change its group with CrudRepository save method following exception occured :
org.hibernate.HibernateException: identifier of an instance of Group was altered from 2 to 1 and that shows spring data want to edit Group's id field while it is not corrent and i want to change reference of it not its id.
For Update i get view data in a DTO form and after that i convert dto object to entity object using Dozer Mapper as described below:
DozerBeanMapper mapper = new DozerBeanMapper();
// get user by id from database for editing
User user = this.userService.findByIdAndDeletedFalse(form.getId());
// merge view data and entity data using Dozer
mapper.map(form, user);
// save User entity
this.userService.save(user);
userService is a bean that call crudRepository save method only.
Any Solution?
Thanks

The error occurs because you are trying to change the id of a tracked entity, which is not allowed in JPA. You need to obtain a reference to the new Group and assign it to User.group.
In short, instead of this:
user.getGroup().setId(newId);
Try this:
user.setGroup(entityManager.getReference(Group.class, newId));

It's because you are using a transient Group. You need to fetch your group and then assign the group to your user object.

I think you should tell Dozer to exclude group field using the following xml.
<field-exclude>
<a>group</a>
</field-exclude>
And the set the group manually.
mapper.map(form, user);
user.setGroup(this.groupService.finfById(form.group.getId()));
// save User entity
this.userService.save(user);
If you don't want to map the group in your controller. You could use a DozerConverter class where you can fetch the group object and set it there.
<mapping>
<class-a>org.mypackage.Form</class-a>
<class-b>org.mypackage.User</class-b>
<field custom-converter-id="org.mypackage.UserGroupCustomConverter">
<a>groupId</a>
<b>group</b>
</field>
</mapping>
public class UserGroupCustomConverter extends DozerConverter<Form, User> {
public NewDozerConverter() {
super(Form.class, User.class);
}
public Boolean convertTo(Form form, User user) {
user.setGroup(this.groupService.finfById(form.group.getId()));
}
}

Related

Spring boot JPA update to update only specific fields

So I encountered this issue with updating an entity in DB. while Passing a whole entity and updating only specific fields it treats untouched fields as null, as a result I get an exception since those fields are #Not-Null,
I have tried looking for similar problems but could not fix my problem.
Company ENTITY:
#Entity
#Table (name = "companies")
#Data
#ToString(exclude = "perfumes")
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#NotNull
private String name;
#NotNull
#Email(message = "Wrong input. please enter a VALID email address")
private String email;
#NotNull
#Size(min = 4, max = 14, message = "Password range must be between 4 - 14 digits")
private String password;
#NotNull
#Enumerated(EnumType.STRING)
private Country country;
#Singular
#OneToMany(mappedBy = "company", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Perfume> perfumes = new ArrayList<>();
}
Most fields are #NotNull for creation, however, I need to update the entity, sometimes only specific fields.
Service:
#Override
public String updateCompany(int id, Company company) throws DoesNotExistException {
if(!companyRepository.existsById(id))
{
throw new DoesNotExistException(id);
}
companyRepository.saveAndFlush(company);
return company.getName() + " has been UPDATED";
}
as you can see an ENTITY has been passed which causes rest of attributes to be automatically null if not modified.
Controller:
#PutMapping("/updateCompany/{id}")
#ResponseStatus(HttpStatus.ACCEPTED)
public String updateCompany(#PathVariable int id, #RequestBody Company company) throws DoesNotExistException {
return admin.updateCompany(id,company);
}
EXCEPTION:
Validation failed for classes [com.golden.scent.beans.Company] during update time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=password, rootBeanClass=class com.golden.scent.beans.Company, messageTemplate='{javax.validation.constraints.NotNull.message}'}
]
Thanks.
The controller is binding the values you pass in to a new Company entity. The new entity is not attached to the persistence context, it does not have the state of the pre-existing entity. When you save it JPA thinks you want to null out all the fields you don't have values for.
Instead, you could have the controller bind its arguments to a DTO. Then in the service you look up the existing Customer, using findById, and copy the fields you want updated from the DTO to the entity. Then call saveAndFlush passing in the updated entity.
It looks like there's an improvement over the DTO, you can use aJsonPatch to hold the updates passed in, see https://www.baeldung.com/spring-rest-json-patch. The patch method seems like a better match for what you're doing anyway.
On the server the important thing is to look up the existing entity so that you have an entity that is attached to the persistence context and has all its fields current.

Spring repository saveAll inserting duplicate rows for mapped entity

I am trying to insert a list of entities which have one to one relation to another entity. It is possible that the one to one mapped object would be same for many parent entity. I am expecting that the same child entity is referred in foreign keys of parent, but actually duplicate rows are getting created. Here are my Entities.
#Builder
#Entity
public class PaymentInfoType1 {
#Id
Long id;
LocalDate date;
#Column(precision = 15, scale = 2)
BigDecimal amount;
String reference;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "account", referencedColumnName = "id")
Account account;
}
#Builder
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Account {
#Id
Long id;
#EqualsAndHashCode.Include
String name;
#EqualsAndHashCode.Include
String accountId;
}
I am creating a list of PaymentInfoType1 based on the information received from a different system. Each PaymentInfoType1 get created along with its Account, which could have exactly the same info but different objects in realtime.
When i do:
PaymentInfoType1 first = // Created with some logic
Account account1 = // name = sample & accountId = 123
first.setAccount(account1);
PaymentInfoType1 second = // Created with some logic
Account account2 = // name = sample & accountId = 123
second.setAccount(account2);
// Both the above its own account object but the field have exactly same values.
List<PaymentInfoType1> list = List.of(first, second);
repo.saveAll(list);
I was expecting that there will be two rows in PaymentInfoType1 table and one in Account, but found that Account also has two rows. Looks like Equals and HashCode does not have any effect in this case.
How can handle this to not insert duplicate rows when the mapping objects are similar by equals/hashcode.
JPA does nothing with #EqualsAndHashcode (that just generates class methods equals and hashCode).
JPA identifies entities by entity id annotated with #Id (or #EmebeddedId) and this id is also something that can be implemented and checked - and usually also generated (like some db sequence) - in the database level.
If you want to use Account identified by name and accountId on JPA side you need to use #EmbeddedId and #Embeddable and get rid of #Id. This would be something like:
#Embeddable
public class AccountId {
String name;
String accountId; // maybe needs renaming...
}
and then in the Account:
#EmbeddedId
AccountId accountId;
See this for example

How to fetch data before commit in Spring Data JPA?

I'm saving two entity related to each other. After it, I can get the first entity, but I get a NullPointerException when I try to get the second entity from the first entity. This is the example:
#Entity
#Table(name = "PARAMETRIZACION")
public class Parametrizacion {
#Id
#Column(name = "id_param", unique = true, nullable = false)
private Integer idParam;
#OneToMany(fetch = FetchType.LAZY)
private List<Arreglo> listArreglo;
}
And
#Entity
#Table(name = "ARREGLO")
public class Arreglo {
#Id
#Column(name = "id_arreglo", unique = true, nullable = false)
private Integer idArreglo;
}
And my Service:
#Service
#Repository
public class TestServiceImpl implements TestService {
#Override
#Transactional(rollbackFor = Exception.class)
public void methodTest(){
...
parametrizacionRepository.saveAndFlush(parametrizacion);//Id=1
...
arregloRepository.saveAndFlush(listArreglo);//Id=1
Parametrizacion paramFetch = parametrizacionRepository.findById(1);
Log.info("Param.Id=" + paramFetch.getIdParam());
Log.info("Size=" + paramFetch.getListArreglo().size());
}
}
The result for first log is: Param.Id=1
The result for second log is: NullPointerException
How can I get the full entity including his childrens? Only If I do this query after commit transaction I can get the data but I need Save data, Update data and Find data before do Commit on finish transaction.
Maybe there is a problem with the unidirectional relationship. Try adding some #ManyToOne field in the Arreglo class and declare how should they match by adding mappedBy="" to the #OneToMany annotation.
There are some nice examples how the relations should look like:
https://en.wikibooks.org/wiki/Java_Persistence/OneToMany
What you are doing is saving parametrizacion and listArreglo separately. And this don't set any relation for parametrizacion with Arreglo. You have to set listArreglo to parametrizacion's listArreglo variable and save only parametrizacion.

How does Hibernate work with #OneToOne and Cascade.ALL? (using Spring)

I have a class Customer that has a OneToOne bidirectional relationship with a Subscription:
#Entity
#Table(name = "customers")
public class Customer{
#OneToOne(mappedBy="customer",cascade = CascadeType.ALL)
private Subscription currentSubscription;
}
#Entity
#Table(name = "subscriptions")
public class Subscription {
#Id
#Column(columnDefinition = "INT8",name="id", unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="customer"))
private Long id;
#OneToOne
#PrimaryKeyJoinColumn
private Customer customer;
}
Now, when I create a customer with a subscription and call persist on the customer, it nicely saves the subscription as well into the database. However when I have already persisted a customer, and want to add a subscription, it fails with the following error:
Caused by: org.hibernate.id.IdentifierGenerationException: attempted
to assign id from null one-to-one property
[com.qmino.miredot.portal.domain.Subscription.customer]
I've written a test in order to explain what I want to achieve:
#Test
public void shouldCascadeUpdateSubscription(){
Customer owner = customerRepository.save(CustomerMother.getCustomer(false));
Subscription subscription = SubscriptionBuilder.create()
.setBillingDayOfMonth(LocalDate.now().getDayOfMonth())
.setSubscriptionPlan(subscriptionPlan)
.build();
subscription.setCustomer(owner);
owner.setCurrentSubscription(subscription);
customerRepository.save(owner);
Customer result = customerRepository.findOne(owner.getId());
assertThat(result.getCurrentSubscription(),is(notNullValue()));
assertThat(result.getCurrentSubscription().getId(),is(result.getId()));
}
Where did I go wrong?
Cascade here is not the problem, Cascade indicates the action to be done by entity when deleted or updated. What is correct if you want to save complete entity. But for that, you need to have the correct data, your message suggest it tries to update the Customer entity but it founds an empty AccountDetails, so in order to correctly fetch the other entities, you need to add FecthType.EAGER, to get all attributes of mapped entities.
#OneToOne(mappedBy="customer",cascade = CascadeType.ALL, fetch = FetchType.EAGER))

JPA OneToOne relationship

I am building a project using the Play framework and I am having trouble getting my head around JPA #OneToOne relationships.
I currently have two classes:
User Object
#Entity
#Table( name="users" )
public class Users extends Model {
#OneToOne( mappedBy="userId", fetch=FetchType.LAZY, cascade = CascadeType.ALL )
#ForeignKey( name="userId", inverseName="userId" )
UserSettings userSettings;
public userId;
public userName;
}
UserSettings
#Entity
#Table( name="user_settings" )
public class UserSettings extends Model {
#OneToOne( cascade = CascadeType.ALL,targetEntity=User.class )
public String userId;
public String xml;
public UserSettings( String userId ){
this.userId = userId;
}
}
The idea is that I am trying to set the userId field within User as a foreign key within UserSettings. I have tried a few different ways to achieve this and my code always throws an error. The most common error I recveive is:
Referenced property not a (One|Many)ToOne.
However, When I try to set the userId in UserSettings using the code above, I receive the following exception:
A javax.persistence.PersistenceException has been caught, org.hibernate.PropertyAccessException: could not get a field value by reflection getter of reader.User.id
Can anybody help explain how I can achieve my desired goal?
Read section 5.2 of the hibernate reference about the difference between entities and values. You're trying to map a String as an entity. Only entities can be a (One|Many)ToOne, as the error is telling you. I.e., instead of String userId, you should be using User user, and instead of mappedBy="userId", mappedBy="user".
If you extend Model, Play will generate an primary key "id" by default for each entity. If this is not what you want you should extend Generic model instead.
The simplest way would be to have user as a property of user settings:
#Entity
#Table( name="user_settings" )
public class UserSettings extends Model{
#OneToOne
public Users user;
...
#Entity
#Table( name="users" )
public class Users extends Model {
#OneToOne(cascade = CascadeType.ALL)
public UserSettings settings;
Maintaining User and UserSettings in each entity with OneToOne allows you to have bi-directional searches.
If you want to use your own key change from Model to GenericModel and defined your foreign key on the user object.

Categories

Resources