I have an SpringBoot application in which I have defined an entity as given below
#Entity
public class Organisation {
#Id
#GeneratedValue
#JsonIgnore
private Long id;
private String entityId;
#OneToMany(mappedBy = "parent")
#Where(clause = "active_ind=true")
#JsonIgnore
private Set<Organisation> activeSubOrgs = new HashSet<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parentId")
private Organisation parent;
public Set<Organisation> getActiveSubOrgs() {
return activeSubOrgs;
}
In my service class I have a function to fetch the children
public Set<Organisation> getChildrenForEntity(String entityId) {
Organisation parent = organisationRepository.findByEntityIdAndActiveInd(entityId, true);
return parent.getActiveSubOrgs();
}
This works fine and gets the children when called from rest controller, but when I use the same function to test in junit, it always returns empty. In my sql trace log I see that query is not being triggred when getActiveSubOrgs() is being called. My junit test is as given below
#SpringBootTest
#RunWith(SpringRunner.class)
#Transactional
public class OrgServiceTest {
#Autowired
private OrganisationService organisationService;
#Before
public void setup() {
Organisation company = new Organisation("c", true);
company = organisationRepository.save(company);
Organisation circle = new Organisation("circle1", true);
circle.setParent(company);
circle = organisationRepository.save(circle);
Organisation div1 = new Organisation("div1", true);
div1.setParent(circle);
div1 = organisationRepository.save(div1);
}
#Test
public void getChildrenForEntitySuccessTest() {
Set<Organisation> children = organisationService.getChildrenForEntity("c");
System.out.println(children.iterator().next().getEntityId());
assertEquals("circle1", children.iterator().next().getEntityId());
}
The children Set in the test is empty when it should actually have circle1. I have tried calling Hibernate.initialize() on children, but that did not work either.
The issue is that bidirectional relationships have to be updated on both sides, i.e. the parent and the children have to know about each other. In your function setup(), you just define the parent of the child. Therefore, each child knows about its parent. However, the parent does not know about its children.
For bidirectional relationships, a good way of handling this is to define a function for one class to set/add a property and automatically update the other one. In case of OneToMany relationships, this can be handled nicely with add(entity) functions.
public void addActiveSubOrg(Organisation activeSubOrg) {
this.activeSubOrgs.add(activeSubOrgs);
activeSubOrg.setParent(activeSubOrg);
}
Related
I have checked different sources but none solve my problem, such as:
https://coderanch.com/t/671882/databases/Updating-child-DTO-object-MapsId
Spring + Hibernate : a different object with the same identifier value was already associated with the session
My case: I have created 2 classes, 1 repository as below:
#Entity
public class Parent{
#Id
public long pid;
public String name;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
public List<Child> children;
}
-------------------------------------------------------------------
#Entity
public class Child{
#EmbeddedId
public PK childPK = new PK();
public String name;
#ManyToOne
#MapsId("parentPk")
#JoinColumn(name = "foreignKeyFromParent")
public Parent parent;
#Embeddable
#EqualsAndHashCode
static class PK implements Serializable {
public long parentPk;
public long cid;
}
}
------------------------------------------------------------------------
public interface ParentRepository extends JpaRepository<AmazonTest, Long> {
}
Where Parent and Child has One To Many relationship.
In my main method:
public static void main(String[] args) {
#Autowired
private ParentRepository parentRepository;
Parent parent = new Parent();
parent.pid = 1;
parent.name = "Parent 1";
Child child = new Child();
List<Child> childList = new ArrayList<>();
child.childPK.cid = 1;
child.name = "Child 1";
childList.add(child);
parent.children= childList;
parentRepository.save(parent);
parentRepository.flush();
}
When I run the application for the first time, data can successfully saved to the database. But if I run it again, it gives error "Exception: org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session".
I was expecting if the data is new, it will update my database, if data is the same, nothing happen. What's wrong with my code.
If I made parent stand alone (without any relationship with the child). It will not give any error even I rerun the application.
Edited: However, if I use the below implementation with simple primary key in Child Entity, it will work as I expected. I can rerun the application without error. I can also change the value, such as the child.name and it will reflect in database.
#Entity
public class Parent{
#Id
public long pid;
public String name;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
public List<Child> children;
}
-------------------------------------------------------------------
#Entity
public class Child{
#Id
public long cid;
public String name;
#ManyToOne
#JoinColumn(name = "foreignKeyFromParent")
public Parent parent;
}
------------------------------------------------------------------------
public interface ParentRepository extends JpaRepository<AmazonTest, Long> {
}
-------------------------------------------------------------------------
public static void main(String[] args) {
#Autowired
private ParentRepository parentRepository;
Parent parent = new Parent();
parent.pid = 1;
parent.name = "Parent 1";
Child child = new Child();
List<Child> childList = new ArrayList<>();
child.cid = 1;
child.name = "Child 1";
childList.add(child);
parent.children= childList;
parentRepository.save(parent);
parentRepository.flush();
}
Well, parent.pid is your database primary key. You can only save one recordset to the database with id=1. This is expected behaviour.
Maybe make yourself familiar with #GeneratedValue in order to avoid setting the id yourself.
Before full explaination a little note: try to post code that actually compiles and works as advertised.
Your main() does not compile,
you dont set up full relation between Parent and Child.
Also try to explicitely demarcate transactions in the posted example.
How your code works
You are calling save on a repository. Underneath, this method calls entityManager.merge() as you have set an id yourself. Merge calls SQL Select to verify if the object is there, and subsequently calls SQL insert or update for the object. (The suggestions that save with the object with id that exists in db are wrong)
In the first run, the object is not there.
you insert parent
merge is cascaded and you insert child (lets call it childA)
In the second run
merge selects parent (with childA)
We compare if new parent is already in the session.
This is done in SessionImpl.getEntityUsingInterceptor
parent is found
merge is cascaded to the child
again, we check if the object is already in the session.
Now the difference comes:
Depending on how you set up the relation between child and parent, the child may have an incomplete PK (and rely on filling it from the relation to parent annotated with #MapsId). Unfortunately, the entity is not found in the session via the incomplete PK, but later, when saving, the PK is complete, and now, you have 2 confilicting objects with the same key.
To solve it
Child child = new Child();
child.parent = parent;
child.childPK.cid = 1;
child.childPK.parentPk = 1;
This also explains why the code works when you change the PK of Child to a long - there is no way to screw it up and have an incomplete PK.
NOTE
The solution above makes mess with orphans.
I still think that the original solution is better as the orphans are removed.
Also, adding updated soution to original solution is a worthwhile update.
Removing entire list and re-inserting it is not likely perform well under load.
Unfortunalely it removes the list on the first merge of the parent, and re-adds them on the second merge of the parent. (This is why clear is not needed)
Better still, just find the parent entity and make the updates on it (as other answers suggest).
Even better, try to look at the solution and add / replace only specific children of the parent, not lookig at the parent and its children ollection. This will be likely most performant.
Original Solution
I propose the following (note that total replacement of the chilren list is not allowed, as it is a hibernate proxy).
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Child> children = new ArrayList<>();
#SpringBootTest
public class ParentOrphanRepositoryTest {
#Autowired
private ParentOrphanRepository parentOrphanRepository;
#Test
public void testDoubleAdd() {
addEntity();
addEntity();
}
#Transactional
public void addEntity() {
Parent parent = new Parent();
parent.pid = 1;
parent.name = "Parent 1";
parent = parentOrphanRepository.save(parent);
Child child = new Child();
List<Child> childList = new ArrayList<>();
child.parent = parent;
child.childPK.cid = 1;
child.name = "Child 1";
childList.add(child);
// parent.children.clear(); Not needed.
parent.children.addAll(childList);
parentOrphanRepository.save(parent);
parentOrphanRepository.flush();
}
}
im having a problem when adding a new entry in a many-to-many relationship because the list is huge. Ex:
Item item = new Item(1);
Category cat = dao.find(1, Category.class);
List<Category> list = new ArrayList<>();
list.add(cat);
item.setCategoryList(list);
cat.getItemList().add(item);
The problem is that the Category Itens list is huge, with a lot of itens, so performing the cat.getItemList() takes a very long time. Everywhere i look for the correct way to add a many-to-many entry says that a need to do that. Can someone help?
Edit:
A little context: I organize my itens with tags, so 1 item can have multiple tags and 1 tag can have multiple itens, the time has pass and now i have tags with a lot of itens ( > 5.000), and now when i save a new item with one of thoses tags it takes a long time, i have debuged my code and found that most of the delay is in the cat.getItensList() line, with makes sense since it has a extensive list o itens. I have searched a lot for how to do this, and everyone says that the correct way to save a entry in a many-to-many case is to add to the list on both sides of the relationship, but if one side is huge, it will takes a lot of time since calling the getItensList() loads them in the context. Im looking for a way to save my item refering the tag witout loading all of the itens of that tag.
Edit 2:
My classes:
Item:
#Entity
#Table(name = "transacao")
#XmlRootElement
public class Transacao implements Serializable {
#ManyToMany(mappedBy = "transacaoList")
private List<Tagtransacao> tagtransacaoList;
...(other stuff)
}
Tag:
#Entity
#Table(name = "tagtransacao")
#XmlRootElement
public class Tagtransacao implements Serializable {
#JoinTable(name = "transacao_has_tagtransacao", joinColumns = {
#JoinColumn(name = "tagtransacao_idTagTransacao", referencedColumnName = "idTagTransacao")}, inverseJoinColumns = {
#JoinColumn(name = "transacao_idTransacao", referencedColumnName = "idTransacao")})
#ManyToMany
private List<Transacao> transacaoList;
...(other stuff)
}
Edit 3:
WHAT I DID TO SOLVE:
As answered by Ariel Kohan, i tried to do a NativeQuery to insert the relationship:
Query query = queryDAO.criarNativeQuery("INSERT INTO " + config.getNomeBanco() + ".`transacao_has_tagtransacao` "
+ "(`transacao_idTransacao`, `tagtransacao_idTagTransacao`) VALUES (:idTransacao, :idTag);");
query.setParameter("idTransacao", transacao.getIdTransacao());
query.setParameter("idTag", tag.getIdTagTransacao());
I was able to reduce the time of que query from 10s to 300milis what it is impressive. In the end its better for my project that it is already runnig to do that instead of creating a new class that represents the many-to-many reletionship. Thanks to everyone who tried to help \o/
In this case, I would prevent your code from load the item list in memory.
To do that, I can think about two options:
Using a #Modyfing query to insert the items directly in the DB.
[Recommended for cases where you want to avoid changing your model]
You can try to create the query using normal JPQL but, depending on your model, you may need to use a native query. Using native query would be something like this:
#Query(value = "insert into ...", nativeQuery = true)
void addItemToCategory(#Param("param1") Long param1, ...);
After creating this query, you will need to update your code removing the parts where you load the objects in memory and adding the parts to call the insert statements.
[Update]
As you mentioned in a comment, doing this improved your performance from 10s to 300milis.
Modify your Entities in order to replace #ManyToMany with #OneToManys relationship
The idea in this solution is to replace a ManyToMany relationship between entities A and B with an intermediate entity RelationAB. I think you can do this in two ways:
Save only the Ids from A and B in RelationAB as a composite key (of course you can add other fields like a Date or whatever you want).
Add an auto-generated Id to RelationAB and add A and B as other fields in the RelationAB entity.
I did an example using the first option (you will see that the classes are not public, this is just because I decided to do it in a single file for the sake of simplicity. Of course, you can do it in multiple files and with public classes if you want):
Entities A and B:
#Entity
class EntityA {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public EntityA() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
#Entity
class EntityB {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public EntityB() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
RelationABEntity and RelationABId:
#Embeddable
class RelationABId implements Serializable {
private Long entityAId;
private Long entityBId;
public RelationABId() {
}
public RelationABId(Long entityAId, Long entityBId) {
this.entityAId = entityAId;
this.entityBId = entityBId;
}
public Long getEntityAId() {
return entityAId;
}
public void setEntityAId(Long entityAId) {
this.entityAId = entityAId;
}
public Long getEntityBId() {
return entityBId;
}
public void setEntityBId(Long entityBId) {
this.entityBId = entityBId;
}
}
#Entity
class RelationABEntity {
#EmbeddedId
private RelationABId id;
public RelationABEntity() {
}
public RelationABEntity(Long entityAId, Long entityBId) {
this.id = new RelationABId(entityAId, entityBId);
}
public RelationABId getId() {
return id;
}
public void setId(RelationABId id) {
this.id = id;
}
}
My Repositories:
#Repository
interface RelationABEntityRepository extends JpaRepository<RelationABEntity, RelationABId> {
}
#Repository
interface ARepository extends JpaRepository<EntityA, Long> {
}
#Repository
interface BRepository extends JpaRepository<EntityB, Long> {
}
A test:
#RunWith(SpringRunner.class)
#DataJpaTest
public class DemoApplicationTest {
#Autowired RelationABEntityRepository relationABEntityRepository;
#Autowired ARepository aRepository;
#Autowired BRepository bRepository;
#Test
public void test(){
EntityA a = new EntityA();
a = aRepository.save(a);
EntityB b = new EntityB();
b = bRepository.save(b);
//Entities A and B in the DB at this point
RelationABId relationABID = new RelationABId(a.getId(), b.getId());
final boolean relationshipExist = relationABEntityRepository.existsById(relationABID);
assertFalse(relationshipExist);
if(! relationshipExist){
RelationABEntity relation = new RelationABEntity(a.getId(), b.getId());
relationABEntityRepository.save(relation);
}
final boolean relationshipExitNow = relationABEntityRepository.existsById(relationABID);
assertTrue(relationshipExitNow);
/**
* As you can see, modifying your model you can create relationships without loading big list and without complex queries.
*/
}
}
The code above explains another way to handle this kind of things. Of course, you can make modifications according to what you exactly need.
Hope this helps :)
This is basically copied from a similar answer I gave earlier but similar question as well. The code below ran when I first write it but I changed the names to match this question so there might be some typos. The spring-data-jpa is a layer on top of JPA. Each entity has its own repository and you have to deal with that. For dealing with the many-to-many relations specifically in spring-data-jpa you can make a separate repository for the link table if you think it's a good idea.
#Entity
public class Item {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ItemCategory> categories;
#Entity
public class Category {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ItemCategory> items;
#Entity
public class ItemCategory {
#EmbeddedId
private ItemcategoryId id = new ItemcategoryId();
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("itemId")
private Item Item;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("categoryId")
private Category category;
public ItemCategory() {}
public ItemCategory(Item Item, Category category) {
this.item = item;
this.category = category;
}
#SuppressWarnings("serial")
#Embeddable
public class ItemCategoryId implements Serializable {
private Long itemId;
private Long categoryId;
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ItemCategoryId that = (ItemCategoryId) o;
return Objects.equals(itemId, that.itemId) && Objects.equals(categoryId, that.categoryId);
}
#Override
public int hashCode() {
return Objects.hash(itemId, categoryId);
}
And to use it. Step 3 shows the way you are currently doing it and creates a read of the existing joins before doing the update. Step 4 just inserts a relation directly in the join table and does not cause a pre-read of the existing joins.
#Transactional
private void update() {
System.out.println("Step 1");
Category category1 = new Category();
Item item1 = new Item();
ItemCategory i1c1 = new ItemCategory(Item1, Category1);
categoryRepo.save(Category1);
ItemRepo.save(Item1);
ItemCategoryRepo.save(p1t1);
System.out.println("Step 2");
Category category2 = new Category();
Item item2 = new Item();
ItemCategory p2t2 = new ItemCategory(item2, category2);
ItemRepo.save(item2);
categoryRepo.save(category2);
ItemCategoryRepo.save(p2t2);
System.out.println("Step 3");
category2 = CategoryRepo.getOneWithitems(2L);
category2.getitems().add(new ItemCategory(item1, category2));
categoryRepo.save(Category2);
System.out.println("Step 4 -- better");
ItemCategory i2c1 = new ItemCategory(item2, category1);
itemCategoryRepo.save(i2c1);
}
I don't explicitly set the ItemCategoryId id's. These are handled by the persistence layer (hibernate in this case).
Note also that you can update ItemCategory entries either explicity with its own repo or by adding and removing them from the list since CascadeType.ALL is set, as shown. The problem with using the CascadeType.ALL for spring-data-jpa is that even though you prefetch the join table entities spring-data-jpa will do it again anyway. Trying to update the relationship through the CascadeType.ALL for new entities is problematic.
Without the CascadeType neither the items or categories lists (which should be Sets) are the owners of the relationship so adding to them wouldn't accomplish anything in terms of persistence and would be for query results only.
When reading the ItemCategory relationships you need to specifically fetch them since you don't have FetchType.EAGER. The problem with FetchType.EAGER is the overhead if you don't want the joins and also if you put it on both Category and Item then you will create a recursive fetch that gets all categories and items for any query.
#Query("select c from Category c left outer join fetch c.items is left outer join fetch is.Item where t.id = :id")
Category getOneWithItems(#Param("id") Long id);
I found weird Hibernate behavior that I can not understand.
Let's say I have class A (was inspired with this question JPA: How to have one-to-many relation of the same Entity type)
#Entity
public class A {
#Id
private String id;
private String name;
#ManyToOne
#JoinColumn(name = "PARENT")
private A parent;
#OneToMany(mappedBy = "parent",cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE })
private Set<A> children;
// Getters, Setters, etc...
}
Also, say we have Spring JPA Repository
public interface ARepository extends JpaRepository<A, Long> {}
And test class where magic happens
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:spring/applicationContext-test.xml" })
public class ATest {
#Autowired
private ARepository aRepository;
#Test
public void testA() {
A parent = new A();
parent.setName("I am Parent: 121_1001_21");
parent.setId("121_1001_21");
A son = new A();
son.setName("I am Son: 121_1001_31");
son.setId("121_1001_31");
son.setParent(parent);
A daughter = new A();
daughter.setName("I am Daughter: 121_1001_32");
daughter.setId("121_1001_32");
daughter.setParent(son);
// daughter.setParent(parent);// yes, I'm intentionally creates wrong hierarchy
parent.setChildren(new HashSet<>(Arrays.asList(daughter, son)));// order of elements in set matters!
son.setChildren(new HashSet<>(Arrays.asList(daughter)));
aRepository.save(parent);
}
}
So the hierarchy is following:
Parent (121_1001_21)
Son (121_1001_31)
Daughter (121_1001_32)
Daughter (121_1001_32)
But this test fails on saving entity with
javax.persistence.EntityNotFoundException:
Unable to find com.orga.pcom.om.core.entity.productorder.A with id 121_1001_31
After hours of debugging I found that Hibernates tries to load linked entities and load it in this way:
Parent (121_1001_21) 1st load
Son (121_1001_31) 3rd load (this entity loading is fail!)
Daughter (121_1001_32) 2nd load
Daughter (121_1001_32) 2nd load
and fails! So, the questions are:
Why Hibernate loads something while it saves something? :)
What is the best way to fix this issue?
Page entity.
#Entity
#Table(name = "pages", schema = "admin")
public class Page implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true)
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne(targetEntity = Partition.class, fetch = FetchType.LAZY)
private Partition partition;
#Column(name = "is_startable")
private Boolean isStartable;
#Column(name = "priority")
private Integer priority;
#Column(name = "prefix_granted_authority")
private String prefixGrantedAuthority;
#OneToMany(mappedBy = "page", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Permission> permissions;
#Column(name = "link", unique = true)
private String link;
PageRepository
List<Page> findByPermissionsGroupsOrderByPartitionNameAscNameAsc(#Param(value = "group") Group group);
PageServiceImpl
#Override
public Collection<Page> getAccessedPages(Group group) {
try {
List<Page> pages = pageRepository.findByPermissionsGroupsOrderByPartitionNameAscNameAsc(group);
return pages;
} catch (Exception ex) {
logger.error("getPage error", ex);
return null;
}
}
getAccessedPages return real List of page entities(not null), but all fields in entities are null.
Why?
I also encounter this problem while ago, it looks like spring data does some kind lazy instantiation.
So if you not access this fields inside of your transaction, they will stay null. Add annotation #Transactional on method where are you calling this request and problem will be solved.
I wanted to expand on #user902383's answer, which ultimately also solved my issue, but it was too long for a comment.
In my case, I had repository method fetching an entity, Helper, called inside a #PostLoad listener that used Helper for calculations for filling a field in another entity, Child. The listener method was already annotated with org.springframework.transaction.annotation.Transactional.
When called by Child's repository it fetched a Helper entity with all fields filled, but when called by the repository of an entity Parent which had a child Child, it fetched an empty Helper object with only the id filled even though it was properly annotated.
The issue was that I was using this hack to access the repository outside of a Spring #Component (I couldn't make the listener a component). I suspect that the Spring magic for detecting when a field is dereferenced in a #Transactional method does not work when the repository was not properly #Autowired. I still do not know why it worked in Child's repository but not in Parent.
My solution to this particular problem was moving the repository call and dereferencing to a #Service, which properly #Autowires the repository, and doing the hackish static call for getting that service instead, which makes for better code structure anyway.
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).