No session during serialization of lazy objects in Spring Boot 2 - java

I'm migrating my Spring Boot REST API from 1.5.4 to 2.0.3.
These are my two entities, a repository for one of them and a controller for accessing them:
Parent.java
#Entity
#Table(name = "PARENT")
public class Parent implements Serializable {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<Child> children;
}
Child.java
#Entity
#Table(name = "CHILD")
public class Child implements Serializable {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Column(name = "PARENT_ID")
private Long parentId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID")
private Parent parent;
#Column(name = "name")
private String name;
}
ParentRepository.java
public interface ParentRepository extends JpaRepository<Parent, Long> {
}
ParentController.java
#RestController
#RequestMapping("/parents")
public class ParentController {
#Autowired
private ParentRepository parentRepository;
#RequestMapping(method = RequestMethod.GET)
public List<Parent> getParents() {
return parentRepository.findAll();
}
}
It appears that there is no longer an active session in the #RestController classes since
parentRepository.findAll().get(0).getChildren().get(0).getName();
now throws a
LazyInitializationException: failed to lazily initialize a collection of role: com.mycompany.myapplication.entity.Parent.children, could not initialize proxy - no Session
This can be fixed by setting a #Transactional annotation on either the controller method or the controller class.
However, the problem I have regards the lazily loaded children.
If I run the example code above, even with the #Transactional annotation, I get the same exception but with a nested
com.fasterxml.jackson.databind.JsonMappingException
This is due to the serialization to JSON happens outside of the controller, hence outside the active session.
There is an ugly fix for this, by reading some data from each child before exiting the method:
#RequestMapping(method = RequestMethod.GET)
public List<Parent> getParents() {
List<Parent> parents = parentRepository.findAll();
parents.stream()
.flatMap(p -> p.getChildren().stream())
.forEach(Child::getName);
return parents;
}
This works, but is terribly ugly and adds a lot of boilerplate.
Another solution would be to map all entities to DTOs before returning them to the client. But this solution adds another layer to my application which I don't want.
Is there a way to make sure that there is an active session during the automagical serialization of the entities?

Soo yeaah...
During migration I had previously set
spring.jpa.open-in-view = false
because I saw a new warning about it in the log. This setting removes the active session I wanted help adding...
Removing this setting and using the default (true) fixed my problem entirely.

Related

LazyInitializationException after return updated entity

I have some model with two relations:
#Entity
#Table(name = "data_model")
public class DataModel {
#Id
#GeneratedValue
#Column(name = "model_id")
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "dataModel", cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE})
private List<OutputField> outputFields;
#OneToMany(mappedBy = "dataModel", cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE})
private List<Query> queries;
//some another fields
}
I use Spring Data JPA and I want to update entity. I write simple service:
#Service
public class DataModelService {
#Autowired
private DataModelRepository dataModelRepository;
#Transactional
public DataModel createOrUpdate(DataModel dataModel) {
return dataModelRepository.save(dataModel);
}
//another methods
}
I write simple test:
public class DataModelServiceTest {
#Autowired
private DataModelService dataModelService;
#Test
void shouldUpdateDataModel() {
DataModel dataModelBeforeUpdate = dataModelService.getById(1);
dataModelBeforeUpdate.getQueries().get(0).setSqlQuery("SELECT 1");
DataModel updatedModel = dataModelService.createOrUpdate(dataModelBeforeUpdate);
assertThat(updatedModel.getQueries(), notNullValue());
}
}
But, I get error, when I try to call method getQieries():
Unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception.
In debug I see:
Questions:
Why does this error occur and how can I fix it? How do I make hibernate return all links after an update?
Why is the outputFields field filled in correctly, but the queries field is not?
It happens because you are trying to initialize collection outside a transaction. To fix this add #DataJpaTest and #RunWith(SpringRunner.class) annotations to your test class. By default, data JPA tests are transactional.
Refer here for more details.

Spring boot JPA - Lazy loading is not working for One to One mapping

Please note that I have looked at similar questions and I have explained why they haven't worked for me
I have a simple Spring boot JPA-Hibernate application with one to one mapping between User and Address. (Please note that I do not have this issue with one to many mapping)
User Entity
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private Address address;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Note> notes;
}
Address Entity
#Entity
#Table(name = "addresses")
public class Address implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String street;
#Column
private String city;
#JsonIgnore
#OneToOne
#JoinColumn(name = "user_id")
private User user;
}
Note Entity
#Entity
#Table(name = "notes")
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
My problem is that whenever I call the controller mapped to get all users I was getting the address and all the associated notes with it as well. But I would expect FetchType.LAZY to take care of that.
I read a lot of questions on StackOverflow mentioning that Jackson might be the culprit here:
Post 1
I also read that spring.jpa.open-in-view defualt value might be the culprit:
Post 2
Post 3
So i tried the following options:
I disabled default open in view property by adding spring.jpa.open-in-view=false to my application.properties which started giving me
Could not write JSON: failed to lazily initialize a collection of role error
I am assuming its because Jackson is calling the getters on my lazily loaded objects so I followed the instructions from another post and added the following for Jackson to leave the lazily loaded collections alone:
pom.xml
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.9</version>
</dependency>
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) {
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(new Hibernate5Module());
}
}
}
}
This solution above fixed the issue with the One to Many mapping but still has the Address associated in the response.
I am not sure what can I do here. The User Entity on the default landing page does not need any address details so I do not want to load it on the landing page. When the record is clicked then it navigates to another page and that's where I would like all the lazy loaded objects to be returned in the response.
I have tried everything I could find online but still nothing has worked so far. I would really appreciate some help with this.
As mentioned by one of the users that it might a duplicate of another question on SO:
Suggested Possible duplicate
I would like to mention that I got the Lazy loading working by disabling spring.jpa.open-in-view property but adding
mapper.registerModule(new Hibernate5Module());
brings back the address associated to the User in the response.
It's working as in the JPA spec:-
Refer the below URL
https://javaee.github.io/javaee-spec/javadocs/javax/persistence/FetchType.html
LAZY fetching strategy is only a hint (as the javadoc says the data can be lazily fetched).. not a mandatory action.
Eager is mandatory (as the javadoc says the data must be eagerly fetched).
You may take a look at Jackson Serialization Views.
I´ve taken a look into the Hibernate5 module you tried and it has some interesting features... but none should fix this issue out of the box for you.
By the way, I normally fix this issue by not returning the Entity as the response but DTOs instead.
The problem is jackson triggering initialization when he writes the JSON, so just don't write the current field (address). But you should not use #jsonIgnore so at other places you could return an Eager obj.
You can use the #jsonView annotation that can provide different JSON for the same obj at different requests. You can look this example :
Create view class:
public class ViewFetchType {
static class lazy{ }
static class Eager extends lazy{ }
}
Annotate your Entity
#Entity
public class User {
#Id
#JsonView(ViewFetchType.Lazy.class)
private String id;
#JsonView(ViewFetchType.Eager.class)
#OneToOne( fetch = FetchType.LAZY)
private Address address ;
}
Specify the FetchType class in your controller:
public class UserController {
private final UserRepository userRepository;
#Autowired
UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#RequestMapping("get-user-details")
#JsonView(ViewFetchType.Eager.class)
public #ResponseBody Optional<User> get(#PathVariable String email) {
return userRepository.findByEmail(email);
{
#RequestMapping("get-all-users")
#JsonView(ViewFetchType.Lazy.class)
public #ResponseBody List<User> getUsers() {
return userRepository.findAll();
}
}
Here is the answer that i took the idea from... https://stackoverflow.com/a/49207551/10162200

Jackson #JsonView for long chain of associations on JPA/Hibernate entity graph

I have the following code:
public interface JSONInvoiceView {
public interface JSONInvoiceBasicView {
}
public interface JSONInvoiceWithLinesView extends JSONInvoiceBasicView {
}
}
#PersistenceUnit(unitName="ERP_PU")
#Entity
#Table(name="INVOICE")
public class Invoice extends FrameworkEntity {
#Id
#SequenceGenerator(name = "PK_INVOICE_GEN", sequenceName = "PK_INVOICE_GEN", allocationSize=1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PK_INVOICE_GEN")
#Column(name = "ID")
#JsonView(JSONInvoiceView.JSONInvoiceBasicView.class)
private Long id;
#OneToMany(mappedBy="invoiceLine", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesView.class)
#JsonManagedReference
private List<InvoiceLine> lines = new ArrayList<InvoiceLine>();
#Temporal(TemporalType.DATE)
#Column(name = "DATE")
#JsonView(JSONInvoiceView.JSONInvoiceBasicView.class)
private Date startDate;
//...
}
#PersistenceUnit(unitName="ERP_PU")
#Entity
#Table(name="INVOICE_LINE")
public class InvoiceLine extends FrameworkEntity {
#Id
#Column(name = "ID")
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesView.class)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name="FK_INVOICE")
#JsonBackReference
private Invoice invoice;
#Column(name = "AMOUNT")
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesView.class)
private BigDecimal amount;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name="FK_GOOD")
private Good good;
//...
}
#PersistenceUnit(unitName="ERP_PU")
#Entity
#Table(name="GOOD")
public class Good extends FrameworkEntity {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "DESCRIPTION", length=200)
private String description;
//...
}
So – one Invoice can have multiple InvoiceLines and each line has reference to Good. I need to get two JSON views: Inovice-only view and Invoice+InvoiceLine-only view. My domain is far richer than these 3 classes – the whole entity graph involves tens of classes and I need careful control how much of this graph I am loading in my entities. But I need to control also how much of loaded graph the JSON serialization facility should try to serialize. And I have the problem with this second control.
entityList is list of Invoices which has loaded InvoiceLines (with touch, e.g. invoiceLines.size();) but InvoiceLines have not further loaded Goods (invoiceLine.good is not touched during lazy load). So, entityList if Invoice+InvoiceLines.
I use the following code for Invoice-only view and this code works:
jsonString = objectMapper.writerWithView(JSONInvoiceView.JSONInvoiceBasicView.class).writeValueAsString(entityList);
Code for retrieving JSON view with Invoice+InvoiceLine-only data:
jsonString = objectMapper.writerWithView(JSONInvoiceView.JSONInvoiceWithLinesView.class).writeValueAsString(entityList);
And this code does not work, it raises error message:
org.codehaus.jackson.map.JsonMappingException: could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->mycom.entities.Invoice["invoiceLines"]->org.hibernate.collection.internal.PersistentBag[0]-> mycom.entities.Good["good"]-> mycom.entities.Good_$$_jvst4f9_c["id"])
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:218)
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:183)
at org.codehaus.jackson.map.ser.std.SerializerBase.wrapAndThrow(SerializerBase.java:140)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:158)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:286)
So, the question is – what Jackson views/annotations should I apply to serialized Invoice+InvoiceLine only parts of entity graph which has loaded only Invoice+InvoiceLine data? How should I indicate that Jackson should not try to go further along association chain and Jackson should not try to serialize 3rd, 4th and so order associations, Jackson should not try to serialize good entities?
p.s. Ignore annotations (or any similar global annotation on entities) is not applicable in my case, because there will be cases when I need only Invoice data and then there will be cases when I will need Invoice+InvoiceLine+Good data and further I will need data Invoice+InvoiceLine+Good+GoodSupplier, etc.
I have found solution - Jackson perceives fields without #JsonView annotation as the fields belonging to every view. Therefor I should introduce additional view:
public interface JSONInvoiceView {
public interface JSONInvoiceBasicView {
}
public interface JSONInvoiceWithLinesView extends JSONInvoiceBasicView {
}
public interface JSONInvoiceWithLinesViewExt extends JSONInvoiceWithLinesView {
}
}
And apply new interace to the Good field:
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name="FK_GOOD")
#JsonView(JSONInvoiceView.JSONInvoiceWithLinesExtView.class)
private Good good;
So - I should define new JSON view interfeice for each level of associations for my entities. After appling #JsonView all works like a charm.

Spring JPA repository method does'nt set values of entity after query

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.

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