Can I update only one relation (one field) using Hibernate - java

I have next relations:
#Entity
#Table(name = "STOCK", uniqueConstraints = #UniqueConstraint(columnNames = { "CODE", "INTERVAL", "DATE" }) )
public class StockEntity {
.....
many other fields like collections
.....
#ElementCollection
#CollectionTable(name = "PARAMETERS", joinColumns = #JoinColumn(name="SETTING_ID"))
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#MapKeyEnumerated(EnumType.STRING)
private Map<LocalSetting.SettingType, LocalSetting> parameters = new HashMap<>();
}
#Entity
#Table(name = "LOCAL_SETTING")
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class LocalSetting {
#Id
#Column(name = "SETTING_ID", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long settingId;
.....
}
when I use my DAO:
#Override
public void updateStocks(List<StockEntity> stocks) {
Session session = sessionFactory.getCurrentSession();
stocks.forEach(stock -> {
session.update(stock);
session.flush();
session.clear();
});
}
in log trace i notice that will be updated all fields in StockEntity, when I was changing only "parameters",
it mean i also was updating lot of other parameters which i could simply skip
when I try to update only "parameters" using next code:
#Override
public void updateParameters(List<StockEntity> stocks) {
stocks.forEach(stock -> {
for (Map.Entry<LocalSetting.SettingType, LocalSetting> entry : stock.getParameters().entrySet()) {
sessionFactory.getCurrentSession().saveOrUpdate(entry.getValue());
}
});
}
but process will be same like i will update whole StockEntity
,maybe I can detach another fields until I will finish update "parameters"

As specified here
session.update()
update takes an entity as parameter.But you are passing a Map (which I assume are entities of LocalSetting that you want to save ).
And as far as I know it is not possible to persist a collection of objects using the update(..) method.
Try the following code to check if it works for you:
#Override
public void updateParameters(Map<LocalSetting.SettingType, LocalSetting> parameters)
{
for (Map.Entry<String, String> entry : map.entrySet())
{
sessionFactory.getCurrentSession().update(entry.getValue());
}
sessionFactory.getCurrentSession().commit();
sessionFactory.getCurrentSession().close();
}
What makes me suggest this answer more is the exception in your question(org.hibernate.MappingException) which is basically telling us that we are updating the entity which isnt mapped.

You have to use dynamic-insert in order to update only one field. The syntax is:
#org.hibernate.annotations.Entity(
dynamicInsert = true
)
You can see an example here: Dynamic insert example
You also can read this post why hibernate dynamic insert false by default in order to know problem you can have with dynamicInsert=true

Related

Bidirectional Entities - Overwriting an object in database and hibernate is doing insert instead of update

I have an issue when trying to update the contents of a cart with new values added one by one. I am using Spring boot with Hibernate, JPA Repositories, MySQL Database and a front-end built with vanilla JS.I will describe my issue in the following lines.
I am having an user entity that looks like this:
#Entity
#Table(name = "users")
#Getter
#Setter
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
#Column(name = "cartprod_id")
#OneToMany(cascade = {CascadeType.ALL})
private List<CartItem> cartProduct;
This entity has a List<CartItem> field that looks like this:
#Entity
#Getter
#Setter
public class CartItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "product_id")
private int productId;
#JsonIgnore
#ManyToOne
private User user;
}
And the relationship between them in the Database looks like in this image:
The idea of the above code is to have a way to keep the state of the products Cart of an user.
The flow is as follows :
I am doing a request from the front-end each time a user adds a product in the cart, when this happens, I am saving the contents of the cart(that consist of only the products ID's) in the database using the CartItem entity.
The problem
With this approach, instead of saving only the last product added to the cart(or replace the cart completely with the new values similar to an update), it is inserting the same object over and over again but with all the new appended values instead of overwriting the old object(table) in the database. An example of this would be in this first image . As you can see I have added a product to the cart with id 327 first.
Next I am adding a product with id 328 but it also adds 327 a second time, this goes on and on, the more products I add. This last code snippet contains my controller code .
#PostMapping("/savecart")
public ResponseEntity<String> saveCartToDb(#RequestBody List<CartItem> cartItemList, Principal principal){
System.out.println(cartItemList);
User logedInUser = userService.findUserByUsername(principal.getName()).get();
List<CartItem> cartItem = logedInUser.getCartProduct();
if(cartItem.isEmpty()){
logedInUser.setCartProduct(cartItemList);
userService.saveNewUser(logedInUser);
}else {
cartItem = cartItemList;
logedInUser.setCartProduct(cartItem);
userService.saveNewUser(logedInUser);
}
// userService.saveNewUser(logedInUser);
return ResponseEntity.ok().body("ASD");
}
How can I overwrite the contents of the List<CartItems> for the user so that I will not append new and old values again and again ? Would a SET help as it won't allow duplicates ?
I have also tried this How do I update an entity using spring-data-jpa? but I a not sure that I need to create a #Query for this issue.
I managed to make it work. The solution is the following.
This is a bidirectional one-to-many / many-to-one issue. In order to be able to remove child elements from the parent, we need to decouple(detach) them from the parent. Since parent and child are bound together by a foreign key the detachment has to be done on both ends. If one has a reference to the other this will not work so BOTH REFERENCES have to be removed. 2 methods are needed and both need to be called when doing decoupling. This is what I used.
private Set<CartProduct> cartProduct;
This one to be added in the parent class (User).
public void removeChild(CartProduct child) {
this.cartProduct.remove(child);
}
This one to be added in the Child class
public void removeParent() {
this.user.removeChild(this);
this.user = null;
}
Methods also have to be called like this
for(CartProduct cartItem : cartItemList){
cartItem.removeParent();
logedInUser.removeChild(cartItem);
}
L.E
It may be that with the above implementation you will get a
java.util.ConcurrentModificationException: null
it happen to me in one of the cases too. In order to fix this I used an Iterator like below.
for (Iterator<CartProduct> iterator = cartItemList.iterator(); iterator.hasNext();) {
CartProduct cartItem = iterator.next();
if (cartItem != null) {
iterator.remove();
}
}

Hibernate many-to-many association not updating join table

In my app, I have a many-to-many association between the User and Preference entities. Since the join table requires an additional column, I had to break it down into 2 one-to-many associations as such:
User entity :
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade={CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
public Set<UserPreference> getPreferences()
{
return preferences;
}
Preference entity :
#OneToMany(mappedBy = "preference", fetch = FetchType.EAGER)
public Set<UserPreference> getUserPreferences()
{
return userPreferences;
}
UserPreference entity :
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
public User getUser()
{
return user;
}
public void setUser(User user)
{
this.user = user;
}
#ManyToOne
#JoinColumn(name = "preference_id", nullable = false)
public Preference getPreference()
{
return preference;
}
public void setPreference(Preference preference)
{
this.preference = preference;
}
#Column(nullable = false, length = 25)
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
To update one of the preferences, I loop through the user's set of preferences and update the value as such:
#RequestMapping(value = {"/edit-{id}-preference"}, method = RequestMethod.POST)
public String updateUserPreference(#ModelAttribute("userPreference") UserPreference preference, BindingResult result, ModelMap model)
{
User loggedInUser = (User)session.getAttribute("loggedInUser");
for (UserPreference pref : loggedInUser.getPreferences())
{
if (Objects.equals(pref.getId(), preference.getId()))
{
pref.setValue(preference.getValue());
}
}
userService.update(loggedInUser);
return "redirect:/users/preferences";
}
I have confirmed that the user variable I'm trying to update does indeed contain the new value after this code runs. Even weirder, the value does update on the webpage when the redirect happens but the database does NOT update! This is the code I'm using to do the update, this class is annotated with #Transactional and every other call to this method (to update the user's role for example) works perfectly:
#Override
public void update(User user)
{
User entity = dao.findById(user.getId());
if (entity != null)
{
entity.setUserId(user.getUserId());
entity.setPassword(user.getPassword());
entity.setFirstName(user.getFirstName());
entity.setLastName(user.getLastName());
entity.setRole(user.getRole());
entity.setPreferences(user.getPreferences());
}
}
This acts like hibernate's session "cache" has the updated value but does not actually persist it. I am using this very same update method style for about 30 other entities and everything works fine. This is my only many-to-many association that I had to break down into 2 one-to-many associations so I have nothing to compare to.
Am I doing something wrong? When I create a user with a new HashSet and persist it, the value is written correctly in the "join table".
*****EDIT*****
For comparison, this is the code I use to create a new user with default preferences. The preferences exist already but the join table is completely empty and this code correctly persists the entities:
User user = new User();
user.setUserId("admin");
user.setPassword(crypter.encrypt("admin"));
user.setFirstName("admin");
user.setLastName("admin");
user.setRole(roleService.findByName("Admin"));
Set<UserPreference> userPreferences = new HashSet<>();
Preference preference = preferenceService.findByName("anchorPage");
UserPreference userPreference = new UserPreference();
userPreference.setUser(user);
userPreference.setPreference(preference);
userPreference.setValue("System Statistics");
userPreferences.add(userPreference);
preference = preferenceService.findByName("showOnlyActivePatients");
userPreference = new UserPreference();
userPreference.setUser(user);
userPreference.setPreference(preference);
userPreference.setValue("true");
userPreferences.add(userPreference);
user.setPreferences(userPreferences);
userService.save(user);
Thanks
Instead of
entity.setPreferences(user.getPreferences());
Do something like:
for( UserPreference uf : user.getPreferences() ) {
entity.getPreferences().add( uf );
}
The main difference here is that you aren't changing the list reference, which is managed by Hibernate, and is only adding elements to it.
How about using merge? That is what you are cascading after all and you have modified a detached object and need to merge back the changes:
public void update(User user) {
dao.merge(user);
}
EDIT: for clarity this replaces the old update method, so it should be called from the client side with loggedInUser, just like before.
EDIT 2: as noted in the comments merge will update all fields. The old update method also seems to do that? Optimistic locks (version numbers) can be used to guard against overwriting other changes by mistake.

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.

object references an unsaved transient instance - save the transient instance before flushing : Spring Data JPA

I have Below 3 models :
Model 1: Reservation
#Entity
public class Reservation {
public static final long NOT_FOUND = -1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#OneToMany(mappedBy = "reservation", cascade = CascadeType.ALL, orphanRemoval = true)
public List<RoomReservation> roomReservations = new ArrayList<>();
}
Model 2: Room Reservation:
public class RoomReservation extends{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "RESERVATION_ID")
public Reservation reservation;
#OneToMany(mappedBy = "roomReservation", cascade = CascadeType.ALL, orphanRemoval = true)
public List<GuestDetails> guestDetails = new ArrayList<>();
}
Model 3 : Guest Details:
public class GuestDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public Long guestId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ROOM_RESERVATION_ID")
public RoomReservation roomReservation;
public Boolean isPrimary;
#Transient
public Guest guest;
}
The Relationship between those three are as :
Reservation --One to Many on RESERVATION_ID--> Room Reservation --One to Many on ROOM_RESERVATION_ID--> Guest Details
I am getting the reservation object and trying to update guest details i get the following error:
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.model.GuestDetails.roomReservation -> com.model.RoomReservation
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1760)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:82)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
... 73 common frames omitted
I have changed cascadeType to ALL as suggested in common question still getting the same error.Please donot make it duplicate as i have tried all the solution realated to this kind of question already asked
Please Let me know what mistake i am doing. Thanks
Code to save Reservation Object by changing GuestDetails:
Reservation existingReservation = reservationRepository.findOne(reservationId);
Reservation reservation = reservationParser.createFromJson(reservationNode);
existingReservation.roomReservations.forEach(roomReservation -> {
RoomReservation updatedRoomReservation = reservation.roomReservations.stream().filter(newRoomReservation -> Objects.equals(roomReservation.id, newRoomReservation.savedReservationId)).findFirst().orElse(null);
if(updatedRoomReservation != null){
roomReservation.guestDetails = updatedRoomReservation.guestDetails;
}
});
reservationRepository.save(existingReservation);
... save the transient instance before flushing :
com.model.GuestDetails.roomReservation -> com.model.RoomReservation
This exception states clearly that RoomReservation contained in GuestDetails, does not exist in the database (and most likely it's id is null).
In general, you can solve this exception either by :
Saving RoomReservation entity before saving GuestDetails
Or making cascade = CascadeType.ALL (or at least {CascadeType.MERGE, CascadeType.PERSIST}) for #ManyToOne GuestDetail-->RoomReservation
But first, I have a couple of points to cover:
Do not use public fields in your class, this violates the encapsulation concept.
While you have a bidirectional association, you can set the other side of the association in your Setter methods.
For your case, you should change RoomReservation class :
public class RoomReservation{
//..... other lines of code
#OneToMany(mappedBy = "roomReservation", cascade = CascadeType.ALL, orphanRemoval = true)
private List<GuestDetails> guestDetails = new ArrayList<>();
public void setGuestDetails(List<GuestDetails> guestDetails) {
this.guestDetails.clear();
// Assuming that by passing null or empty arrays, means that you want to delete
// all GuestDetails from this RoomReservation entity
if (guestDetails == null || guestDetails.isEmpty()){
return;
}
guestDetails.forEach(g -> g.setRoomReservation(this));
this.guestDetails.addAll(guestDetails);
}
public List<GuestDetails> getGuestDetails() {
// Expose immutable collection to outside world
return Collections.unmodifiableList(guestDetails);
}
// You may add more methods to add/remove from [guestDetails] collection
}
Saving the Reservation:
Reservation existingReservation = reservationRepository.findOne(reservationId);
Reservation reservation = reservationParser.createFromJson(reservationNode);
existingReservation.roomReservations.forEach(roomReservation -> {
Optional<RoomReservation> updatedRoomReservation = reservation.roomReservations.stream().filter(newRoomReservation -> Objects.equals(roomReservation.id, newRoomReservation.savedReservationId)).findFirst();
if(updatedRoomReservation.isPresent()){
// roomReservation already exists in the database, so we don't need to save it or use `Cascade` property
roomReservation.setGuestDetails( updatedRoomReservation.get().getGuestDetails());
}
});
reservationRepository.save(existingReservation);
Hope it helps!
This can be caused by incorrect transaction semantics.
If the referenced instance was not fetched in the current transaction it counts as transient.
The easiest solution is to add #Transactional to the method:
#Transactional
public void saveReservation(...) {
Reservation existingReservation = reservationRepository.findOne(reservationId);
Reservation reservation = reservationParser.createFromJson(reservationNode);
// ...
reservationRepository.save(existingReservation);
}
GuestDetails - add the needed CasadeType:
#ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.ALL)
#JoinColumn(name = "ROOM_RESERVATION_ID")
public RoomReservation roomReservation;
RoomReservation - add the nedded CascadeType:
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.AL)
#JoinColumn(name = "RESERVATION_ID")
public Reservation reservation;
And then you need to persist the data before/after using the for-each loop. Depends on you safe()-Method.
Reservation reservation = reservationParser.createFromJson(reservationNode);
entityManager.persist(reservation);
And then safe it afterwards. Tell me your result. Maybe directly working without changing/adding the cascadetypes.
You can save the reservation you get from the Json.
JPA will update the rows with the same id's.
The error you get is because the guestDetails has still a reference to the updatedRoomReservation.
If you don't want to save the whole reservation from the json you have to set the right RoomReservation.
e.g.:
if(updatedRoomReservation != null){
roomReservation.guestDetails = updatedRoomReservation.guestDetails;
guestDetails.forEach(guestDetail -> guestDetail.roomReservation = roomReservation);
}
If you are using JPA 2.0 then defaults fetch type for OneToMany is LAZY. If after your lambda, your updatedRoomReservation is null (as you set in orElse) then existingReservation.roomReservation.guestDetails will never be loaded and will be null.
Therefore when you save existingReservation, you get the error.

Delete Not Working with JpaRepository

I have a spring 4 app where I'm trying to delete an instance of an entity from my database. I have the following entity:
#Entity
public class Token implements Serializable {
#Id
#SequenceGenerator(name = "seqToken", sequenceName = "SEQ_TOKEN", initialValue = 500, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seqToken")
#Column(name = "TOKEN_ID", nullable = false, precision = 19, scale = 0)
private Long id;
#NotNull
#Column(name = "VALUE", unique = true)
private String value;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "USER_ACCOUNT_ID", nullable = false)
private UserAccount userAccount;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "EXPIRES", length = 11)
private Date expires;
...
// getters and setters omitted to keep it simple
}
I have a JpaRepository interface defined:
public interface TokenRepository extends JpaRepository<Token, Long> {
Token findByValue(#Param("value") String value);
}
I have a unit test setup that works with an in memory database (H2) and I am pre-filling the database with two tokens:
#Test
public void testDeleteToken() {
assertThat(tokenRepository.findAll().size(), is(2));
Token deleted = tokenRepository.findOne(1L);
tokenRepository.delete(deleted);
tokenRepository.flush();
assertThat(tokenRepository.findAll().size(), is(1));
}
The first assertion passes, the second fails. I tried another test that changes the token value and saves that to the database and it does indeed work, so I'm not sure why delete isn't working. It doesn't throw any exceptions either, just doesn't persist it to the database. It doesn't work against my oracle database either.
Edit
Still having this issue. I was able to get the delete to persist to the database by adding this to my TokenRepository interface:
#Modifying
#Query("delete from Token t where t.id = ?1")
void delete(Long entityId);
However this is not an ideal solution. Any ideas as to what I need to do to get it working without this extra method?
Most probably such behaviour occurs when you have bidirectional relationship and you're not synchronizing both sides WHILE having both parent and child persisted (attached to the current session).
This is tricky and I'm gonna explain this with the following example.
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToMany(cascade = CascadeType.PERSIST, mappedBy = "parent")
private Set<Child> children = new HashSet<>(0);
public void setChildren(Set<Child> children) {
this.children = children;
this.children.forEach(child -> child.setParent(this));
}
}
#Entity
public class Child {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Long id;
#ManyToOne
#JoinColumn(name = "parent_id")
private Parent parent;
public void setParent(Parent parent) {
this.parent = parent;
}
}
Let's write a test (a transactional one btw)
public class ParentTest extends IntegrationTestSpec {
#Autowired
private ParentRepository parentRepository;
#Autowired
private ChildRepository childRepository;
#Autowired
private ParentFixture parentFixture;
#Test
public void test() {
Parent parent = new Parent();
Child child = new Child();
parent.setChildren(Set.of(child));
parentRepository.save(parent);
Child fetchedChild = childRepository.findAll().get(0);
childRepository.delete(fetchedChild);
assertEquals(1, parentRepository.count());
assertEquals(0, childRepository.count()); // FAILS!!! childRepostitory.counts() returns 1
}
}
Pretty simple test right? We're creating parent and child, save it to database, then fetching a child from database, removing it and at last making sure everything works just as expected. And it's not.
The delete here didn't work because we didn't synchronized the other part of relationship which is PERSISTED IN CURRENT SESSION. If Parent wasn't associated with current session our test would pass, i.e.
#Component
public class ParentFixture {
...
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void thereIsParentWithChildren() {
Parent parent = new Parent();
Child child = new Child();
parent.setChildren(Set.of(child));
parentRepository.save(parent);
}
}
and
#Test
public void test() {
parentFixture.thereIsParentWithChildren(); // we're saving Child and Parent in seperate transaction
Child fetchedChild = childRepository.findAll().get(0);
childRepository.delete(fetchedChild);
assertEquals(1, parentRepository.count());
assertEquals(0, childRepository.count()); // WORKS!
}
Of course it only proves my point and explains the behaviour OP faced. The proper way to go is obviously keeping in sync both parts of relationship which means:
class Parent {
...
public void dismissChild(Child child) {
this.children.remove(child);
}
public void dismissChildren() {
this.children.forEach(child -> child.dismissParent()); // SYNCHRONIZING THE OTHER SIDE OF RELATIONSHIP
this.children.clear();
}
}
class Child {
...
public void dismissParent() {
this.parent.dismissChild(this); //SYNCHRONIZING THE OTHER SIDE OF RELATIONSHIP
this.parent = null;
}
}
Obviously #PreRemove could be used here.
I had the same problem
Perhaps your UserAccount entity has an #OneToMany with Cascade on some attribute.
I've just remove the cascade, than it could persist when deleting...
You need to add PreRemove function ,in the class where you have many object as attribute e.g in Education Class which have relation with UserProfile
Education.java
private Set<UserProfile> userProfiles = new HashSet<UserProfile>(0);
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "educations")
public Set<UserProfile> getUserProfiles() {
return this.userProfiles;
}
#PreRemove
private void removeEducationFromUsersProfile() {
for (UsersProfile u : usersProfiles) {
u.getEducationses().remove(this);
}
}
One way is to use cascade = CascadeType.ALL like this in your userAccount service:
#OneToMany(cascade = CascadeType.ALL)
private List<Token> tokens;
Then do something like the following (or similar logic)
#Transactional
public void deleteUserToken(Token token){
userAccount.getTokens().remove(token);
}
Notice the #Transactional annotation. This will allow Spring (Hibernate) to know if you want to either persist, merge, or whatever it is you are doing in the method. AFAIK the example above should work as if you had no CascadeType set, and call JPARepository.delete(token).
This is for anyone coming from Google on why their delete method is not working in Spring Boot/Hibernate, whether it's used from the JpaRepository/CrudRepository's delete or from a custom repository calling session.delete(entity) or entityManager.remove(entity).
I was upgrading from Spring Boot 1.5 to version 2.2.6 (and Hibernate 5.4.13) and had been using a custom configuration for transactionManager, something like this:
#Bean
public HibernateTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
return new HibernateTransactionManager(entityManagerFactory.unwrap(SessionFactory.class));
}
And I managed to solve it by using #EnableTransactionManagement and deleting the custom
transactionManager bean definition above.
If you still have to use a custom transaction manager of sorts, changing the bean definition to the code below may also work:
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
As a final note, remember to enable Spring Boot's auto-configuration so the entityManagerFactory bean can be created automatically, and also remove any sessionFactory bean if you're upgrading to entityManager (otherwise Spring Boot won't do the auto-configuration properly). And lastly, ensure that your methods are #Transactional if you're not dealing with transactions manually.
I was facing the similar issue.
Solution 1:
The reason why the records are not being deleted could be that the entities are still attached. So we've to detach them first and then try to delete them.
Here is my code example:
User Entity:
#Entity
public class User {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private List<Contact> contacts = new ArrayList<>();
}
Contact Entity:
#Entity
public class Contact {
#Id
private int cId;
#ManyToOne
private User user;
}
Delete Code:
user.getContacts().removeIf(c -> c.getcId() == contact.getcId());
this.userRepository.save(user);
this.contactRepository.delete(contact);
Here we are first removing the Contact object (which we want to delete) from the User's contacts ArrayList, and then we are using the delete() method.
Solution 2:
Here we are using the orphanRemoval attribute, which is used to delete orphaned entities from the database. An entity that is no longer attached to its parent is known as an orphaned entity.
Code example:
User Entity:
#Entity
public class User {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = true)
private List<Contact> contacts = new ArrayList<>();
}
Contact Entity:
#Entity
public class Contact {
#Id
private int cId;
#ManyToOne
private User user;
}
Delete Code:
user.getContacts().removeIf(c -> c.getcId() == contact.getcId());
this.userRepository.save(user);
Here, as the Contact entity is no longer attached to its parent, it is an orphaned entity and will be deleted from the database.
I just went through this too. In my case, I had to make the child table have a nullable foreign key field and then remove the parent from the relationship by setting null, then calling save and delete and flush.
I didn't see a delete in the log or any exception prior to doing this.
If you use an newer version of Spring Data, you could use deleteBy syntax...so you are able to remove one of your annotations :P
the next thing is, that the behaviour is already tract by a Jira ticket:
https://jira.spring.io/browse/DATAJPA-727
#Transactional
int deleteAuthorByName(String name);
you should write #Transactional in Repository extends JpaRepository
Your initial value for id is 500. That means your id starts with 500
#SequenceGenerator(name = "seqToken", sequenceName = "SEQ_TOKEN",
initialValue = 500, allocationSize = 1)
And you select one item with id 1 here
Token deleted = tokenRepository.findOne(1L);
So check your database to clarify that
I've the same problem, test is ok but on db row isn't deleted.
have you added the #Transactional annotation to method? for me this change makes it work
In my case was the CASCADE.PERSIST, i changed for CASCADE.ALL, and made the change through the cascade (changing the father object).
CascadeType.PERSIST and orphanRemoval=true doesn't work together.
Try calling deleteById instead of delete on the repository. I also noticed that you are providing an Optional entity to the delete (since findOne returns an Optional entity). It is actually strange that you are not getting any compilation errors because of this. Anyways, my thinking is that the repository is not finding the entity to delete.
Try this instead:
#Test
public void testDeleteToken() {
assertThat(tokenRepository.findAll().size(), is(2));
Optional<Token> toDelete = tokenRepository.findOne(1L);
toDelete.ifExists(toDeleteThatExists -> tokenRepository.deleteById(toDeleteThatExists.getId()))
tokenRepository.flush();
assertThat(tokenRepository.findAll().size(), is(1));
}
By doing the above, you can avoid having to add the #Modifying query to your repository (since what you are implementing in that #Modifying query is essentially the same as calling deleteById, which already exists on the JpaRepository interface).

Categories

Resources