Using JPA 2.1 and Hibernate 4.3.6.Final, I have the following simple entity:
#Entity
#Table(name = "CONTACT")
public class Contact {
#Id
#Column(name = "ID")
private String id = UUID.randomUUID().toString();
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARTNER_ID")
private Contact partner;
Contact() {
}
public void assignPartner(final Contact other) {
this.partner = Objects.requireNonNull(other);
other.partner = this;
}
public void unassignPartner() {
if (partner != null) {
partner.partner = null;
}
partner = null;
}
}
Notice the lazy-loaded one-to-one recursive association to a partner Contact. Also notice how assignPartner() and unassignPartner() manage the bi-directional relationship.
And the following methods:
private static void assignPartner(final EntityManager entityManager) {
entityManager.getTransaction().begin();
final Contact contact1 = entityManager.find(Contact.class, CONTACT1_ID);
final Contact contact2 = entityManager.find(Contact.class, CONTACT2_ID);
contact1.assignPartner(contact2);
entityManager.getTransaction().commit();
}
private static void unassignPartner(final EntityManager entityManager) {
entityManager.getTransaction().begin();
final Contact contact1 = entityManager.find(Contact.class, CONTACT1_ID);
contact1.unassignPartner();
entityManager.getTransaction().commit();
}
Assuming existing rows for CONTACT1_ID and CONTACT2_ID, after running assignPartner() then unassignPartner(), database state shows that contact1 has a null partner_id and contact2 still has a non-null partner_id.
However, if I change the Contact.partner fetch type to EAGER, after running assignPartner() then unassignPartner(), database state shows that both contact1 and contact2 have null partner_id.
Why is that? Why are changes to the partner entity not flushed to the database?
EDIT 1
Changes to the partner reference through direct field access, e.g. partner.firstName = "DUMPED", are not propagated either.
Changes to the partner reference through method access, e.g. partner.setFirstName("DUMPED"), are propagated.
Neither partner.partner = null or partner.setPartner(null) are propagated.
EDIT 2
As suggested by Rat2000, moving the unassignment logic outside the Contact.unassignPartner() method and inside the unassignPartner(EntityManager) method seems to work properly. So it's really something to do with how Hibernate deals with the contact1.partner proxy, and in particular the contact1.partner.partner proxy.
final Contact contact1 = entityManager.find(Contact.class, CONTACT1_ID);
contact1.getPartner().unassignPartner();
contact1.unassignPartner();
Try this:
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.UPDATE)
#JoinColumn(name = "PARTNER_ID")
private Contact partner;
Related
i'm writing here to have some hind about the solution that make , in short the problem that I've faced is: I have two entity in bidirectional many to many relationship, for instance I've the following Post and Tag entities:
#Data
#Entity
#Table(name = "posts")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/*...*/
#ManyToMany( cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH} )
#JoinTable(name = "post_tag",
joinColumns = #JoinColumn(name = "post_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "tag_id", referencedColumnName = "id"))
#JsonIgnoreProperties("posts")
private Set<Tag> tags = new HashSet<>();
}
#Data
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NaturalId
private String text;
#ManyToMany(mappedBy = "tags")//, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
#JsonIgnoreProperties("tags")
private Set<Post> posts = new HashSet<>();
}
My problem is that in a HTTP POST Action I get the data for the post and a Collection of tags related to it and I have to save all with the condition to not duplicate the tags entity if the "text" is already present in the database. Assuming we have a Map with the given data, the code is as follow below:
Post post = new Post();
String heading = (String) payload.get("heading");
String content = (String) payload.get("content");
post.setHeading(heading);
post.setContent(content);
Set<Tag> toSaveTags = new HashSet<Tag>();
List list = (List) payload.get("tags");
for (Object o : list) {
Map map = (Map) o;
String text = (String) map.get("text");
Tag tag = new Tag();
tag.setText(text);
post.getTags().add(tag);
tag.getPosts().add(post);
log.info("post has {}# tag", post.getTags().size());
toSaveTags.add(tag);
};
//method to save it all
postRepository.saveWithTags(post, toSaveTags);
My solution was to design a Repository class with the method shown above, as follow:
#Repository
public class PostTagsRepositoryImpl implements PostTagsRepository {
#Autowired
private EntityManagerFactory emf;
#Override
public Post saveWithTags(Post post, Collection<Tag> tags) {
EntityManager entityManager = emf.createEntityManager();
post.getTags().clear();
for (Tag tag : tags) {
tag.getPosts().clear();
Tag searchedTag = null;
try {
searchedTag = entityManager.createQuery(
"select t "
+ "from Tag t "
+ "join fetch t.posts "
+ "where t.text = :text", Tag.class)
.setParameter("text", tag.getText())
.getSingleResult();
} catch (NoResultException e) {/* DO NOTHING */}
if (searchedTag == null) {
post.getTags().add(tag);
tag.getPosts().add(post);
} else {
entityManager.getTransaction().begin();
entityManager.detach(searchedTag);
post.getTags().add(searchedTag);
searchedTag.getPosts().add(post);
entityManager.merge(searchedTag);
entityManager.getTransaction().commit();
}
}
entityManager.getTransaction().begin();
entityManager.merge(post);
entityManager.getTransaction().commit();
return post;
}
}
My questions are : Could I implement it better? maybe in a single query/transaction? Could you give me some tips?
Some points:
you link both entities and then clear relations in the repository. As you don't have invariants between them first linkage is useless.
maybe in a single query/transaction?
Single query is not really possible but single transaction is indeed what you need to achieve to avoid inconsistency problems.
Unfortunately cascading merge won't work with naturalid so you have to produce this behavior yourself.
So for each tag verify if it exists:
Session session = entityManager.unwrap(Session.class);
Tag t= session.bySimpleNaturalId(Tag.class).load(“some txt”);
Depending on the result you have to load into the Post object the existing tag one (already on db and recover via bySimpleNaturalId) or the new one. Then cascading merge on Post will do the rest.
You always create new entity manager on each call to your repository. You should overcome this by injection the shared entitymanager directly.
#Autowired
Private EntityManager em;
It is thread safe.
I am using Spring Data and #Transactional annotation(for automatic rollback after tests).
I have simple bidirectional relation between account and user(owning side):
#Entity
#Table(name = "ACCOUNT_T")
public class AccountEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String password;
private String verificationCode;
private Boolean active = false;
#OneToOne(mappedBy = "account", fetch = FetchType.EAGER,
cascade = {CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.DETACH, CascadeType.REFRESH})
private UserEntity user;
}
#Entity
#Table(name = "USER_T")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
private String phone;
private LocalDate birthDate;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinColumn(name = "account_id")
private AccountEntity account;
}
I am using JpaRepositories and fetching is set to eager.
Why sometimes when I get objects from database I can't get their child
objects-null is returned. It depends on from which side I add objects.
I have written simple test using Junit5:
#ExtendWith(SpringExtension.class)
#SpringBootTest
#Transactional
class UserAndAccountRepositoriesTest {
void testA() {
UserEntity userEntity = new UserEntity();
setUserProperties(userEntity);
AccountEntity accountEntity = new AccountEntity();
setAccountProperties(accountEntity); //just setting values for fields
accountEntity.setUser(userEntity);
accountRepository.save(accountEntity);
accountRepository.findAll().get(0).getUser(); //returns user
userRepository.findAll().get(0).getAccount(); //returns null,but should return account related to that user
}
void testB() {
UserEntity userEntity = new UserEntity();
setUserProperties(userEntity);
AccountEntity accountEntity = new AccountEntity();
setAccountProperties(accountEntity);
accountEntity.setUser(userEntity);
accountRepository.save(accountEntity);
accountRepository.findAll().get(0).getUser(); //again returns null,but shouldn't
userRepository.findAll().get(0).getAccount(); //returns account
}
}
Without #Transactional everything works fine - I am not getting null.
What am I doing wrong?
You'd need to set both sides of a relationship for explicitly defining it.
Try adding userEntity.setAccount(accountEntity) during your setup case, this would resolve the issue.
Hibernate won't help you and assume just because you set a -> b, it would set b <- a for you within the other entity.
The reason why it might work without #Transactional is that, without the annotation you are committing your setup data into whatever datasource you are using, and nothing is rollbacked at the end, and since you are selecting data without any id with findAll, you are getting previous user/account entites that have already been committed, some with relationship & some without, thus the random error you are getting.
It is because you are not setting account in userEntity. Please try like following:
userEntity.setAccount(accountEntity);
I will explain why the behavior is different depending on whether your are in a transaction or not :
When you are in a transaction :
a) Any get to fetch an entity A you have created prior to this transaction (so which is already in DB) will return a new object in term of memory adress, and hibernate will resolve its bidirectional relationship, even if you did not set it explicitly.
b) Any get to fetch an entity B you have created earlier in this transaction (so which is not yet in DB) will return the same object in term of memory adress, so it really is the same object, thus if you did not set its bidirectional relationship explicitly, it will not exist until you set it or until the transaction is over (as it will effectively persist B in DB) and you fetch B again.
When you are not in a transaction :
Any get to fetch any entity will behave like described in case a).
Conclusion :
The author was in case b).
Lets say I have two objects, say one is a User object and the other is a State Object. The state object is basically the 50 states of America so it doesn't ever have to change. The user object however has a Collection of States where the user has been. So like this:
#Entity
#Table(name="tbl_users")
class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id", unique=true, nullable = false)
private int id;
#Column(name="user_name")
private String name;
#OneToMany(targetEntity=State.class, orphanRemoval = false)
#Column(name="states")
private Collection<State> states;
//getters and setters
}
and the States entity looks like this:
#Entity
#Table(name="tbl_states")
class State {
#Id
#Column(name="id", unique=true, nullable=false)
private int id;
#Column(name="state")
private String state;
// getters and setters
}
Code for adding user (using hibernate):
public int addUser(User user) {
em.persist(user);
em.flush();
return user.getId();
}
Code for getting state by id:
public State getStateById(int id) {
return em.createQuery("SELECT s FROM State s WHERE s.id =:id, State.class)
.setParameter("id", id)
.getSingleResult();
}
but when I try to create a User and pick several states, I get a PSQLException:
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "uk_g6pr701i2pcq7400xrlb0hns"
2017-06-21T22:54:35.959991+00:00 app[web.1]: Detail: Key (states_id)=(5) already exists.
I tried looking up the Cascade methods to see if I could use any, but Cascade.MERGE and Cascade.PERSIST seem to do the same thing, and the rest I don't think I need (REMOVE, DETACH, etc). My question is:
How do I add states to the User object without having that error?
This code works:
class Example {
#Test
public void workingTest() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("testPU");
EntityManager em = emf.createEntityManager();
// Creating three states
State alabama = new State(state: 'Alabama');
State louisiana = new State(state: 'Louisiana');
State texas = new State(state: 'Texas');
em.getTransaction().begin();
em.persist(alabama);
em.persist(louisiana);
em.persist(texas);
em.getTransaction().commit();
List<State> states = em.createQuery('FROM State').getResultList();
// Assert only three states on DB
assert states.size() == 3;
User userFromAlabama = new User();
User userFromAlabamaAndTexas = new User();
em.getTransaction().begin();
State alabamaFromDB = em.find(State, alabama.getId());
State texasFromDB = em.find(State, texas.getId());
userFromAlabama.getStates().add(alabamaFromDB);
userFromAlabamaAndTexas.getStates().add(alabamaFromDB);
userFromAlabamaAndTexas.getStates().add(texasFromDB);
em.persist(userFromAlabama);
em.persist(userFromAlabamaAndTexas);
em.getTransaction().commit();
states = em.createQuery('FROM State').getResultList();
// Assert only three states on DB again
assert states.size() == 3;
// Assert one user
User userFromDB = em.find(User, userFromAlabama.getId());
assert userFromDB.getStates().size() == 1;
userFromDB = em.find(User, userFromAlabamaAndTexas.getId());
assert userFromDB.getStates().size() == 2;
}
}
#Entity
#Table(name="tbl_users")
class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id
#Column(name="user_name")
private String name
#ManyToMany
private Collection<State> states = Lists.newArrayList()
// Getters and setters
}
#Entity
#Table(name="tbl_states")
class State {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name="state")
private String state;
// Getters and setters
}
You should change your mapping to #ManyToMany!
And you must have 3 tables on DB like this:
TBL_USERS, TBL_STATES and TBL_USERS_TBL_STATES
The TBL_USERS_TBL_STATES table is the default table name that Hibernate uses when a property is annotated with #ManyToMany. If you want to change the tablename of TBL_USERS_TBL_STATES, use the #JoinTable annotation too. See the docs here
With this configuration, you should be able to fetch a State from database, add it to a new User and then persist it. I made a unit test and It works!
In your case it might be better to use a manytomany association with manytomany hibernate dont generate unicity constraint.
Hibernate auto generation scheme behavior is a little bit strange with onetoMany but you can use this workaround.
Try this:
#ManyToMany
#JoinTable(name = "user_state")
private List<State> states;
I have three tables they are Forum,ForumAnswer and ForumAnswerReplay. this table contains #OneToMany relationship. I have Forum table primary key. Based on that primary key I need to get ForumAnswer and ForumAnswerReplay data.
But response is not reaching ui side.
Stack Trace
ResponseStatusExceptionResolver:133 - Resolving exception from handler
[public java.util.List
com.tta.abcd.controller.ForumController.getReplayToAnswer(javax.servlet.http.HttpSession)]:
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write content: Infinite recursion (StackOverflowError)
Forum.java
#Entity
#Table(name="Forum")
public class Forum {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="forumId")
private Long forumId;
#Column(name="question")
private String question;
#Column(columnDefinition="varchar(1000)",name="discription")
private String discription;
#Column(name="postedDate")
private Date postedDate;
#Fetch(value = FetchMode.SELECT)
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "forumId")
#JsonIgnore
private List<ForumAnswer> forumList;
}
ForumAnswer.java
#Entity
#Table(name="ForumAnswer")
public class ForumAnswer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="forumAnswerId")
private Long forumAnswerId;
#ManyToOne
#JoinColumn(name = "forumId",insertable=true, updatable=true,nullable=true)
private Forum forum;
#Column(name="answer")
private String answer;
#Column(name="answerDate")
private Date answerDate;
#Fetch(value = FetchMode.SELECT)
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "forumAnswerId")
#JsonManagedReference
private List<ForumAnswerReplay> forumAnswerReplayList;
}
ForumAnswerReplay.java
#Entity
#Table(name="ForumAnswerReplay")
public class ForumAnswerReplay {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="replayId")
private Long replayId;
#Column(name="replayToAnswer")
private String replayToAnswer;
#Column(name="replayToAnswerDate")
private Date replayToAnswerDate;
#ManyToOne
#JoinColumn(name="forumAnswerId",insertable=true,updatable=true,nullable=true)
#JsonBackReference
private ForumAnswer forumAnswer;
}
Controller Code:
#RequestMapping(value = "/getForumOnIdAnswer", method = RequestMethod.POST)
#ResponseBody
public List<ForumAnswer> getForumAnswerOnId(#RequestBody Long id, HttpSession session,
HttpServletResponse httpServletResponse, HttpServletRequest httpServletRequest) {
List<ForumAnswer> forumAnswer = forumService.getForumAnswerOnId(id);
if (forumAnswer != null) {
return forumAnswer;
}
return forumAnswer;
}
DAO:
public List<ForumAnswer> getForumAnswerOnId(Long id) {
Long forumId = id;
List<ForumAnswer> ForumAnswerTemp = new ArrayList<ForumAnswer>();
List<ForumAnswerReplay> ForumAnswerReplayTemp = new ArrayList<ForumAnswerReplay>();
Long forumAnswerId = null;
ForumAnswer forumTemp = new ForumAnswer();
ForumAnswer forumEntity = new ForumAnswer();
String getForumAnswer = "from ForumAnswer WHERE forumId =:forumId order by answerDate Desc";
Query query = sessionFactory.getCurrentSession().createQuery(getForumAnswer);
query.setParameter("forumId", forumId);
List<ForumAnswer> forumList = query.list();
int count =forumList.size();
if ( count> 0) {
for (int i=1 ; i < count ;i++) {
forumEntity =forumList.get(i);
forumAnswerId = forumList.get(i).getForumAnswerId();
List<ForumAnswerReplay> repltList = getreplayList(forumAnswerId);
if (repltList.size() > 0) {
ForumAnswerReplayTemp.addAll(repltList);
forumEntity.setForumAnswerReplayList(ForumAnswerReplayTemp);
ForumAnswerTemp.add(forumEntity);
} else {
ForumAnswerTemp.add(forumEntity);
}
}
}
return ForumAnswerTemp;
}
public List<ForumAnswerReplay> getreplayList(Long forumAnswerId) {
String getForumAnswer = "from ForumAnswerReplay WHERE forumAnswerId =:forumAnswerId";
Query query = sessionFactory.getCurrentSession().createQuery(getForumAnswer);
query.setParameter("forumAnswerId", forumAnswerId);
List<ForumAnswerReplay> replays = query.list();
return replays;
}
May be you need to use #Transactional annotation on your impl class.
One of the key points about #Transactional is that there are two separate concepts to consider, each with it's own scope and life cycle:
the persistence context
the database transaction
The transactional annotation itself defines the scope of a single database transaction. The database transaction happens inside the scope of apersistence context.
The persistence context is in JPA the EntityManager, implemented internally using an Hibernate Session (when using Hibernate as the persistence provider).
The persistence context is just a synchronizer object that tracks the state of a limited set of Java objects and makes sure that changes on those objects are eventually persisted back into the database.
This is a very different notion than the one of a database transaction. One Entity Manager can be used across several database transactions, and it actually often is.
https://dzone.com/articles/how-does-spring-transactional
Make return type String in your method deceleration and Convert your list to map and create Model object and set your map in model. then return to your desire jsp page.
#ResponseBody
#RequestMapping(value = "/getForumOnIdAnswer", method = RequestMethod.POST)
public String yourList(#RequestBody Long id, Model model){
List<ForumAnswer> forumAnswer = forumService.getForumAnswerOnId(id);
if (forumAnswer != null) {
// convert your list to Map then set in model.
model.addAttribute("forumAnswerMap",listToMap);
}
return "yourJspPage"
`
May be it will help you..
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).