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

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))

Related

How do I prevent deleting reference entities/tables using the JpaRepository?

I'm creating a delete api endpoint for my spring boot application. I tried using the delete() and deleteById() methods provided by the JpaRepository. However, whenever I try to delete a concert, using the ConcertEntity or the concertId, the venue entry associated is deleted from the Venues table. How do I prevent deleting reference entities/tables using the JpaRepository?
My current solution is to set the venue to null before deleting the concert entity. My concertRepositroy extends to JpaRepository.
Current Solution in Service Impl
public void deleteConcert(ConcertEntity e){
e.setVenue(null);
this.concertRepository.delete(e);
}
Concert Entity
#Entity
#Table(name = "CONCERTS")
public class ConcertEntity{
#Id
private UUID concertId;
#Column(name = "ARTIST")
String artist;
#Column(name = "VENUE_ID")
VenueEntity venue;
/*Getters && Setters here...*/
}
Use the proper annotation to define the relationship (#ManyToOne or #OneToOne)
#ManyToOne(optional = true)
#JoinColumn(name = "VENUE_ID")
private VenueEntity venue;
That should not trigger any cascade deletion by default, but you can add the cascade parameter to the #ManyToOne or #OneToOne annotation if you want to customize the behavior.

Spring data save updates ID to null

I am simply trying to perform an update of an entity. However hibernate attempts 2 SQL statements, one to perform the correct update and an unwanted second to update the ID alone to null, which causes my application to fail.
I am using Spring Data alongside Hibernate and when performing an update of an Entity, I see the expected update SQL is performed, however when running the application with SQL Server, a subsequent update is attempted which does the following:
update my_table set id=null where id=?
This fails obviously.
Cannot update identity column 'ID'.
Running the same code with H2 I do not see this second update triggered.
Any idea what might be the cause of this behaviour?
I am extending JpaRepository and using the default save().
Here is a snippet of my entity:
#Table(name = "MY_TABLE")
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String anotherValue;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name="id")
private List<ChildEntity> children = new ArrayList<>();
// getters, builder, private default constructor ...
Snippet building my entity:
MyEntity.newBuilder()
.withId(id)
.withAnotherValue(valueUpdate)
.build();
Repository:
public interface MyRepository extends JpaRepository<MyEntity, Long>
Saving:
myRepository.save(myUpdatedEntity);
As i think of probable cause for this is if you associate two entities with their IDs as foreign keys then hibernate may try to update ID of parent as foreign key of other entity. Its not correct way to associate.
In a one-to-many relation add a foreign key in the many side entity, that have to reference the primary key of the one side entity class.
#Entity
public class MyEntity {
..
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name="id", referencedColumnName = "MYENTITY_ID")
private List<ChildEntity> children = new ArrayList<>();
}

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.

Hibernate OneToMany table not being updated

I have table Animal with OneToMany mapping to table EventAnimal:
#OneToMany(mappedBy = "animal")
public Set<EventAnimal> getEventAnimals() {
return eventAnimals;
}
Table EventAnimal looks like this
#Entity
#Table(name = "eventAnimal")
public class EventAnimal {
#Id
int id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "userEvent_id")
UserEvent userEvent;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "animal_id", nullable=false)
Animal animal;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "eventAnimalClass_id")
EventAnimalClass eventAnimalClass;
}
When I add Event animal to animal and save animal, database is not being updated:
//Create EventAnimal object, set properties
eventAnimal.setUserEvent(newEvent);
eventAnimal.setAnimal(animal);
animal.getEventAnimals().add(eventAnimal);
animalPersistenceService.saveAnimal(animal);
What am I doing wrong?
When I try inserting Event animal, like eventAnimalDao.insert(eventAnimal);
instead of
animalPersistenceService.saveAnimal(animal);
I get exception that "animal_id" does not have default value even though I set it.
What is your ID generation strategy? Are you generating the ids by yourself or you will leave this to the DB? If it will be database put:
#GeneratedValue(strategy=GenerationType.AUTO)
You may want to update the key to Integer instead of int too but don`t think this is the problem.
Also if you want to add event in animal and expect to persist it update your mapping to:
#OneToMany(mappedBy = "animal", cascade = CascadeType.ALL)
I have table Company with OneToMany mapping to table Customer:
//bi-directional many-to-one association to Customer
#OneToMany(mappedBy="company")
private Set<Customer> customer;
I have mapped from tis format:
//bi-directional many-to-one association to Company
#ManyToOne
#JoinColumn(name="company_id")
private Company company;
I have use this code, Sucessfully add,edit and delete function is working.
Add function :
Customer customer = new Customer();
Company company = new Company();
company.setCompanyId(intCompanyId);
customer.setCompany(company);
I resolved the problem. It was rather silly mistake - script didn't remove one foreign key from table and I didn't care to look into SQL table.

Hibernate OneToOne relationship

I have two persistence entity: User and UserDetail. They have one-to-one relationship. I use hibernate annotations. But I am getting in my database several objects of user information for one same user. Apparently my knowledge of Hibernate annotations are not so good to solve this problem.
User class:
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
#Column(name = "PASSWORD")
private String password;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private UserDetail userDetail;
// setters and getters
}
UserDetail class:
#Entity
#Table(name = "USER_DETAIL")
public class UserDetail {
#OneToOne
#JoinColumn(name = "USER_ID")
private User user;
// other fields
}
I use this in my code as follows:
UserDetail userDetail = new UserDetail();
userDetail.setInfo(info);
userDetail.setUser(seventhUser);
hibernateTemplate.saveOrUpdate(userDetail);
And everything works properly. Here's what my table USER_DETAIL:
But when I try to change user information, I get an incorrect behavior. I get following table after I again set user information:
UserDetail newUserDetail = new UserDetail();
newUserDetail.setInfo(newInfo);
newUserDetail.setUser(seventhUser);
hibernateTemplate.saveOrUpdate(newUserDetail);
Why the same two objects of information correspond to one user?? I have One-To-One relationship. How can I avoid this? What am I doing wrong?
If you want to modify an existing UserDetail, then you must set its ID, or get it from the session and modify it. Else, Hibernate thinks it's a new one that must be saved, since it doesn't have any ID.
UserDetail existingUserDetail = session.get(UserDetail.class, theUserDetailId);
existingUserDetail.setInfo(newInfo);
To make sure you don't save two UserDetail instances for the same user, you should add a unique constraint on the USER_ID column of the UserDetail database table.

Categories

Resources