I'm using Spring Data JPA with JpaRepository and I couldn't find an answer to why my entity is detached and I can't save with children entity.
I want to save Recipe but first I need to save Ingredient in another service. Recipe and Ingredient is connected many to many relations by RecipeIngredients object.
#Transactional
public RecipeDto updateRecipe(UserPrincipal userPrincipal, String recipeName, RecipeDto recipeDto) {
Recipe recipeToEdit = recipeRepository.findByName(recipeName).orElseThrow(EntityNotFoundException::new);
recipeToEdit.setIngredients(recipeIngredientsService.refillRecipeIngredientsList(recipeDto.getIngredients(), recipeToEdit));
}
#Entity
public class RecipeIngredients {
#EmbeddedId
private RecipeIngredientsId recipeIngredientsId;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("recipeId")
#ToString.Exclude
private Recipe recipe;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE})
#MapsId("ingredientId")
#ToString.Exclude
private Ingredient ingredient;
}
#Entity
#Getter
#Setter
public class Ingredient {
#Id
#Column(name = "id", updatable = false)
private Long id;
#OneToMany(mappedBy = "ingredient", cascade = CascadeType.ALL, orphanRemoval = true)
#ToString.Exclude
private List<RecipeIngredients> recipeIngredients;
public void addRecipeIngredient(RecipeIngredients recipeIngredient) {
if(recipeIngredients == null) {
recipeIngredients = new ArrayList<>();
}
recipeIngredients.add(recipeIngredient);
recipeIngredient.setIngredient(this);
}
}
Before I want to save Ingredient entity I want to check there is exist so I pull it from external service then map It to DTO object. If there is no Ingredient I want to save it and get his object, which will be connected children RecipeIngredients and this is connected to Recipe so when I'm saving recipe it should save me RecipeIngredient with it of Ingredient.
public void refillRecipeIngredientsList(List<RecipeIngredientsDto> recipeIngredientsDtos, Recipe recipe) {
removeOldIngredientsIfExist(recipe);
if (recipeIngredientsDtos != null) {
for (RecipeIngredientsDto recipeIngredientsDto : recipeIngredientsDtos) {
IngredientDto ingredient = pullSavedIngredient(recipeIngredientsDto);
RecipeIngredients recipeIngredient = this.recipeIngredientsDtoToEntityMapper.recipeIngredientsToEntity(recipeIngredientsDto, recipe, ingredientDto);
recipe.addIngredient(recipeIngredient);
ingredient.addRecipeIngredient(recipeIngredient);
}
}
}
But in every way I received org.hibernate.PersistentObjectException: detached entity passed to persist: com.app.todaysdinner.entity.ingredient.Ingredient. Do anyone know the answer how to reattach Ingredient entity?
[EDIT]
I found out that when I want to update through Recipe cascade on RecipeIngredients also cascade onto Ingredients that means it invoke PERSISTANCE method even if allowed is only cascade = {CascadeType.MERGE}. But I can't find answer why in one #Transaction object is being detached.
Lately my project encountered a really odd issue with JPA findOne(id) returning a proxy object instead of a full object.
Here is the scenario. Consider the entities and their connections shown below.
#Table(name = "HOUSE")
#Entity
#EqualsAndHashCode
#Setter
#ReadPermission(expression = "user has rights for template snapshots AND has filter")
public class HouseEntity extends VersionedEntity {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "house", fetch = FetchType.LAZY, orphanRemoval = true)
public List<RoomEntity> getRooms() {
return rooms;
}
#OneToMany(cascade = CascadeType.ALL, mappedBy = "template", fetch = FetchType.LAZY, orphanRemoval = true)
public List<TableEntity> getTables() {
return tables;
}
}
#Entity
#Table(name = "ROOMS")
public class Room {
#ManyToOne(fetch = FetchType.LAZY)
public HouseEntity getHouse() {
return house;
}
#OneToMany(mappedBy = "room", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
public List<TableEntity> getTables() {
return tables;
}
}
#Entity
#Table(name = "TABLES")
public class TableEntity{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "HOUSE_ID")
public HouseEntity getHouse() {
return template;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ROOM_ID")
public RoomEntity geRoom() {
return room;
}
As you can see, House has Tables and Rooms, Rooms also has Tables, and each child entity has a connection to its parents.
Add a table to the HouseEntity
Remove the table from the HouseEntity immediately after.
For 1, the houseRepository.findById gets my HouseEntity wrapped in proxy, as if it is lazy loaded.
The weird part is that if I do:
Add a table to a RoomEntity, which is a child of HouseEntity.
Remove the table from RoomEntity.
Then houseRepository.findById returns the HouseEntity without the proxy.
My question here is, why would this happen? Why would the findById method return a proxyed entity in this case? I need to have access to the normal entity without the proxy directly, even if the proxy has the entity populated in the target.
I have Many To Many with additional column. Here realization(getters/setters generated by lombok, removed for clarity):
public class User extends BaseEntity {
#OneToMany(mappedBy = "user",
orphanRemoval = true,
fetch = FetchType.LAZY
cascade = CascadeType.ALL,)
private List<UserEvent> attendeeEvents = new ArrayList<>();
}
#Table(
name = "UC_USER_EVENT",
uniqueConstraints = {#UniqueConstraint(columnNames = {"user_id", "event_id"})}
)
public class UserEvent extends BaseEntity {
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "event_id")
private Event event;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_response_id")
private UserResponse userResponse;
}
public class Event extends BaseEntity {
#OneToMany(mappedBy = "event",
orphanRemoval = true,
fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
private List<UserEvent> userEvents = new ArrayList<>();
}
I want this - when i delete Event, All "UserEvents" connected to it should be removed. And if I delete User, all "UserEvents" should be removed too.
I delete my event(eventRepository is Spring Jpa interface):
eventRepository.delete(event);
Then retrieving UserEvents from db:
List<UserEvent> userEvents = userEventsId.stream()
.map(id -> entityManager.find(UserEvent.class, id)).collect(Collectors.toList());
And there is collection with 2 items(this is count of UserEvents), but they all "null".
I can't understand what happening and how to do it right.
I need them deleted and when I check collection there should be 0, instead of 2.
The delete says marked for deletion, please try calling flush after deletion, and then find.
I guess find goes to the database, find the two rows, but when trying to instantiate them, find the entities marked for deletion and then you have this strange behaviour.
Recomendation: try to abstract more from the database and use less annotations. Learn the conventions of names for columns and tables (if you need to) and let JPA do its job.
I have a Many-to-Many relationship between Thread and Participant through a ThreadParticipant entity (because the association has an additional field). I have the following mapping.
Thread entity
#Entity
#Table(name = "thread")
public class Thread {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Collection<ThreadParticipant> threadParticipants = new HashSet<>();
// Getters and setters
}
Participant entity
#Entity
#Table(name = "participant")
public class Participant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = Account.class, cascade = { CascadeType.PERSIST })
#JoinColumn(name = "account_id")
private Account account;
#ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
// Getters and setters
}
ThreadParticipant entity
#Entity
#Table(name = "thread_participant")
#IdClass(ThreadParticipantPK.class)
public class ThreadParticipant implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Participant.class, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "participant_id")
private Participant participant;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Thread.class)
#JoinColumn(name = "thread_id")
private Thread thread;
#Column(name = "last_viewed", nullable = true)
private Date lastViewed;
// Getters and setters
}
ThreadParticipantPK
public class ThreadParticipantPK implements Serializable {
private Thread thread;
private Participant participant;
public ThreadParticipantPK() { }
public ThreadParticipantPK(Thread thread, Participant participant) {
this.thread = thread;
this.participant = participant;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ThreadParticipantPK)) return false;
ThreadParticipantPK that = (ThreadParticipantPK) o;
if (!participant.equals(that.participant)) return false;
if (!thread.equals(that.thread)) return false;
return true;
}
#Override
public int hashCode() {
int result = thread.hashCode();
result = 31 * result + participant.hashCode();
return result;
}
// Getters and setters
}
Now, I am trying to fetch threads with the following query (using Spring Data JPA) and Hibernate as my JPA provider.
#Repository
public interface ThreadRepository extends JpaRepository<Thread, Integer> {
#Query("select distinct t from Thread t inner join fetch t.threadParticipants tp inner join fetch tp.participant p left join fetch p.account a left join fetch p.company c")
public List<Thread> test();
}
The problem is that when the fetch type for the associations within ThreadParticipants are set to FetchType.LAZY, the Thread.threadParticipants collection is empty. Consequently, if I set the associations to FetchType.EAGER, Thread.threadParticipants contains two elements (as it should). In this case, however, Hibernate goes nuts and executes four SQL queries for fetching a single thread.
Hibernate: select thread0_.id as id1_18_0_, threadpart1_.participant_id as particip2_19_1_, threadpart1_.thread_id as thread_i3_19_1_, participan2_.id as id1_12_2_, account3_.id as id1_0_3_, company4_.id as id1_6_4_, thread0_.created as created2_18_0_, thread0_.last_activity as last_act3_18_0_, thread0_.subject as subject4_18_0_, threadpart1_.last_viewed as last_vie1_19_1_, threadpart1_.thread_id as thread_i3_18_0__, threadpart1_.participant_id as particip2_19_0__, threadpart1_.thread_id as thread_i3_19_0__, participan2_.account_id as account_2_12_2_, participan2_.company_id as company_3_12_2_, account3_.email as email2_0_3_, account3_.facebook_profile_id as facebook3_0_3_, account3_.first_name as first_na4_0_3_, account3_.last_name as last_nam5_0_3_, account3_.middle_name as middle_n6_0_3_, company4_.additional_address_text as addition2_6_4_, company4_.banner_name as banner_n3_6_4_, company4_.ci_number as ci_numbe4_6_4_, company4_.city_id as city_id22_6_4_, company4_.co_name as co_name5_6_4_, company4_.company_type_code as company_6_6_4_, company4_.created as created7_6_4_, company4_.description as descript8_6_4_, company4_.email as email9_6_4_, company4_.last_modified as last_mo10_6_4_, company4_.logo_name as logo_na11_6_4_, company4_.name as name12_6_4_, company4_.number_of_reviews as number_13_6_4_, company4_.phone_number as phone_n14_6_4_, company4_.postal_box as postal_15_6_4_, company4_.rating as rating16_6_4_, company4_.second_phone_number as second_17_6_4_, company4_.street_name as street_18_6_4_, company4_.street_number as street_19_6_4_, company4_.teaser as teaser20_6_4_, company4_.website as website21_6_4_ from thread thread0_ inner join thread_participant threadpart1_ on thread0_.id=threadpart1_.thread_id inner join participant participan2_ on threadpart1_.participant_id=participan2_.id left outer join account account3_ on participan2_.account_id=account3_.id left outer join company company4_ on participan2_.company_id=company4_.id
Hibernate: select participan0_.id as id1_12_0_, participan0_.account_id as account_2_12_0_, participan0_.company_id as company_3_12_0_ from participant participan0_ where participan0_.id=?
Hibernate: select thread0_.id as id1_18_0_, thread0_.created as created2_18_0_, thread0_.last_activity as last_act3_18_0_, thread0_.subject as subject4_18_0_ from thread thread0_ where thread0_.id=?
Hibernate: select participan0_.id as id1_12_0_, participan0_.account_id as account_2_12_0_, participan0_.company_id as company_3_12_0_ from participant participan0_ where participan0_.id=?
Apparently it's executing a query for each participant, and two queries for each thread. So, without FetchType.EAGER, my code is simply not working, but with it, my database will get killed. I tried adding a #OneToMany association between Participant and ThreadParticipant (similar to the one from Thread to ThreadParticipant), but with the same results. I also tried to add all of the aliases to my query's field list, but to no avail.
Why is this happening? Is my mapping or query wrong? Thank you in advance!
Try changing the the one-to-many collection to a Set:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Set<ThreadParticipant> threadParticipants = new HashSet<>();
The additional queries should NEVER be generated if you only have LAZY associations. That may be because the many-to-one and one-to-one relationships are EAGER by default. Try setting those to LAZY instead.
I have a many-to-many relationship in Hibernate. When i relate one objectA with two objectsB, the objectA.getObjectB() returns the two elements sucessfully (including database), but objectB.getObjectA() doesn´t return objectA. Only after a new session that is going to work.
#Entity
public class ObjectA implements java.io.Serializable {
private List<ObjectB> objectsB;
...
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "db_objectA_objectB", joinColumns = {
#JoinColumn(name = "idObjectA", updatable = false)}, inverseJoinColumns = {
#JoinColumn(name = "idObjectB", updatable = false)})
public List<ObjectB> getObjectsB() {
return objectsB;
}
public void setObjectsB(List<ObjectB> objectsB) {
this.objectsB = objectsB;
}
}
#Entity
public class ObjectB implements java.io.Serializable {
private List<ObjectA> objectsA;
...
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "objectsB", cascade = CascadeType.ALL)
public List<ObjectA> getObjectsA() {
return objectsA;
}
public void setObjectsA(List<ObjectA> objectsA) {
this.objectsA= objectsA;
}
}
Keeping both sides of the relationship consistent is responsibility of application code, not the responsibility of Hibernate. In JPA 2.0 specification this is told with following words:
Note that it is the application that bears responsibility for
maintaining the consistency of run- time relationships—for example,
for insuring that the “one” and the “many” sides of a bidirectional
relationship are consistent with one another when the application
updates the relationship at runtime.