Implementing orphanRemoval in #OneToMany - spring

So I'm trying to remove the the child in a onetomany relationship but I'm not sure if this is the right way to do. I was reading up how to do it online but many talked about entitymanager, cacasded, using queries etc. I'm unsure on which way to do it, usually I use crudrepository and simply do .save and .deleteById etc.
Here's what I have so far
#Entity
public class User
#OneToMany(orphanRemoval = true, cascade = CascadeType.PERSIST)
private List<Payment>payment = new ArrayList<Payment>();
getters/setters
#Service
public class UserService {
public void addPayment(User user, Payment payment) {
user.getPayment().add(payment);
}
public void removePayment(User user, Payment payment) {
user.getPayment().remove(payment);
}
Do I have to mess with the cascade type or entitymanager here?

Assuming the user method parameter is an managed Entity already associated with the session, then yes, that's the correct way to do it.
When the session gets flushed, Hibernate will delete the Payment instance which was removed from the users payments.

You can try CascadeType.ALL. It work in my case.

Related

How do I model a List of the same Entity in hibernate for a User? (Session, SessionHistory)

I am making a Spring Boot Application with ORM.
I have a user, which has a current session. So modelling these two with #Entity is working great.
We have a Table Users, and Sessions. So a User is an Entity, Session is an Entity. in addition to the current session field that is mapped by a #OnetoOne. There is a SessionHistory class we'd like the user to have. And we would like it to essentially be what it's called. it has all the previous history.
Which brings me to my problem. I don't need to model that in the ERD, I think? So I can't just have it in there. Typically it would be a query that would select all session that have that user's id. How would I translate that into Java code?
#Entity
#Table(name = "users")
#Getter
#Setter
public class User {
#Setter(AccessLevel.NONE)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id", nullable = false)
private Long id;
...
#Transient
private SessionHistory sessionHistory;
public User(...) {
...
// this.sessionHistory = new SessionHistory();
}
public User() {
}
}
I don't know what to try. Because I think it's not a good idea to have a table (it just doesn't make sense to me).
What I did think of, but discussion with my team said it was gonna be a problem. But I suggested Something to do with the SessionRepository, since that's our gateway to the database, but that violates the architecture if I'm not mistaken. TO have that attribute instantiated that way using either the service or repository.
The answer is to make a PostLoadEventListener and check for my entity type. That way I can make a query and set the list of sessions on User load.
Resource that helped a lot It is a little old so just be sure to research the new way of doing things

Spring Data JPA: deleteById does not delete record from database but derived delete method does

I'm observing a kind of strange behavior in my Spring application. Unfortunately I cannot share the complete code, but basically this is what it looks like:
// the repository
#Repository
public interface InboxRepo extends JpaRepository<Inbox, Long> {}
// the service
#Transactional
public void deleteInbox(long id) {
inboxRepo.deleteById(id);
}
When calling deleteInbox(), there is no exception or any kind of error but the Inbox item is not deleted from the database. Setting spring.jpa.show-sql=true shows that there isn't even a DELETE statement, i.e. for whatever reason, the code doesn't actually issue the deletion.
When defining a derived delete method in the repository, then the deletion works, but it doesn't yet make sense to me:
#Repository
public interface InboxRepo extends JpaRepository<Inbox, Long> {
// this seems to work
#Modifying
#Query("delete from Inbox i where i.id = ?1")
void delete(long id);
}
Dleting directly via an EntityManager also works. But what could be the reason that the "standard" JpaRepository methods don't work here?
I found the root cause. There was another entity having a reference to Inbox like this:
#OneToMany(mappedBy = "inbox", cascade = ALL, fetch = FetchType.EAGER)
private Set<Inbox> inbox = new HashSet<>();
The FetchType.EAGER in combination with the cascade caused the problem, i.e. as soon as the Inbox was deleted, this reference caused the Inbox to get "re-persisted". Setting FetchType.LAZY resolved the problem.

Unable to initialize lazy-loaded relationship inside of `#Transactional` method

I have a set of simple models like this (getters and setters omitted for brevity):
#Entity
public class Customer {
#Id
private Integer id;
}
#Entity
public class Order {
#Id
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer_id")
private Customer customer;
}
I am trying to load an Order using a Spring JPA repository with a findById method, including the customer.
First I tried this:
#Transactional
Optional<Order> findById(Integer id) {
return repository.findById(id);
}
But when I tried to access Customer I got a LazyInitializationException: could not initialize proxy - no Session. So after referring to some other questions, I updated my method to be a bit uglier, but to explicitly call Hibernate.initialize:
#Transactional
Optional<Order> findById(Integer id) {
return repository.findById(id)
.map( order -> {
Hibernate.initialize(order.getCustomer());
return order;
);
}
But I still get org.hibernate.LazyInitializationException: could not initialize proxy - no Session. repository is a regular CrudRepository which provides the findById method out-of-the-box.
How can I initialize this lazily loaded child entity? My understanding is that the #Transactional indicates that I should still be within the transaction for the entirety of this method call. The only thing further downstream is the repository itself, which is just an interface, so I'm not sure how else to go about forcing the load of this child entity.
The Order entity and everything else in it is retrieved properly from the database; it's only when I try to get the lazy-loaded child entities that we start having issues.
The only way I managed to get this working was to write a custom hql method in the Repository using a left join fetch. While that works, it clutters up my repository with a method that is pretty much a duplicate of another and which I'm pretty sure I'm not actually supposed to need (so I would rather not do it this way.)
Spring-Boot 2.1.4.RELEASE, Spring 5.1.6.RELEASE, Hibernate 5.3.7.Final.
You have to define the method as public. See "Method visibility and #Transactional" in the spring docs.
This should work:
#Transactional
public Optional<Order> findById(Integer id) {
Optional<Order> order = repository.findById(id);
order.ifPresent(o -> Hibernate.initialize(o.getCustomer()));
return order;
}

LazyInitializationException with graphql-spring

I am currently in the middle of migrating my REST-Server to GraphQL (at least partly). Most of the work is done, but i stumbled upon this problem which i seem to be unable to solve: OneToMany relationships in a graphql query, with FetchType.LAZY.
I am using:
https://github.com/graphql-java/graphql-spring-boot
and
https://github.com/graphql-java/graphql-java-tools for the integration.
Here is an example:
Entities:
#Entity
class Show {
private Long id;
private String name;
#OneToMany(mappedBy = "show")
private List<Competition> competition;
}
#Entity
class Competition {
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Show show;
}
Schema:
type Show {
id: ID!
name: String!
competitions: [Competition]
}
type Competition {
id: ID!
name: String
}
extend type Query {
shows : [Show]
}
Resolver:
#Component
public class ShowResolver implements GraphQLQueryResolver {
#Autowired
private ShowRepository showRepository;
public List<Show> getShows() {
return ((List<Show>)showRepository.findAll());
}
}
If i now query the endpoint with this (shorthand) query:
{
shows {
id
name
competitions {
id
}
}
}
i get:
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role: Show.competitions, could not initialize proxy -
no Session
Now i know why this error happens and what it means, but i don't really know were to apply a fix for this. I don't want to make my entites to eagerly fetch all relations, because that would negate some of the advantages of GraphQL. Any ideas where i might need to look for a solution?
Thanks!
My prefered solution is to have the transaction open until the Servlet sends its response. With this small code change your LazyLoad will work right:
import javax.servlet.Filter;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
/**
* Register the {#link OpenEntityManagerInViewFilter} so that the
* GraphQL-Servlet can handle lazy loads during execution.
*
* #return
*/
#Bean
public Filter OpenFilter() {
return new OpenEntityManagerInViewFilter();
}
}
I solved it and should have read the documentation of the graphql-java-tools library more carefully i suppose.
Beside the GraphQLQueryResolver which resolves the basic queries i also needed a GraphQLResolver<T> for my Showclass, which looks like this:
#Component
public class ShowResolver implements GraphQLResolver<Show> {
#Autowired
private CompetitionRepository competitionRepository;
public List<Competition> competitions(Show show) {
return ((List<Competition>)competitionRepository.findByShowId(show.getId()));
}
}
This tells the library how to resolve complex objects inside my Showclass and is only used if the initially query requests to include the Competitionobjects. Happy new Year!
EDIT 31.07.2019: I since stepped away from the solution below. Long running transactions are seldom a good idea and in this case it can cause problems once you scale your application. We started to implement DataLoaders to batch queries in an async matter. The long running transactions in combination with the async nature of the DataLoaders can lead to deadlocks: https://github.com/graphql-java-kickstart/graphql-java-tools/issues/58#issuecomment-398761715 (above and below for more information). I will not remove the solution below, because it might still be good starting point for smaller applications and/or applications which will not need any batched queries, but please keep this comment in mind when doing so.
EDIT: As requested here is another solution using a custom execution strategy. I am using graphql-spring-boot-starter and graphql-java-tools:
Create a Bean of type ExecutionStrategy that handles the transaction, like this:
#Service(GraphQLWebAutoConfiguration.QUERY_EXECUTION_STRATEGY)
public class AsyncTransactionalExecutionStrategy extends AsyncExecutionStrategy {
#Override
#Transactional
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
return super.execute(executionContext, parameters);
}
}
This puts the whole execution of the query inside the same transaction. I don't know if this is the most optimal solution, and it also already has some drawbacks in regards to error handling, but you don't need to define a type resolver that way.
Notice that if this is the only ExecutionStrategy Bean present, this will also be used for mutations, contrary to what the Bean name might suggest. See https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v11.1.0/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/spring/web/boot/GraphQLWebAutoConfiguration.java#L161-L166 for reference. To avoid this define another ExecutionStrategy to be used for mutations:
#Bean(GraphQLWebAutoConfiguration.MUTATION_EXECUTION_STRATEGY)
public ExecutionStrategy queryExecutionStrategy() {
return new AsyncSerialExecutionStrategy();
}
For anyone confused about the accepted answer then you need to change the java entities to include a bidirectional relationship and ensure you use the helper methods to add a Competition otherwise its easy to forget to set the relationship up correctly.
#Entity
class Show {
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "show")
private List<Competition> competition;
public void addCompetition(Competition c) {
c.setShow(this);
competition.add(c);
}
}
#Entity
class Competition {
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Show show;
}
The general intuition behind the accepted answer is:
The graphql resolver ShowResolver will open a transaction to get the list of shows but then it will close the transaction once its done doing that.
Then the nested graphql query for competitions will attempt to call getCompetition() on each Show instance retrieved from the previous query which will throw a LazyInitializationException because the transaction has been closed.
{
shows {
id
name
competitions {
id
}
}
}
The accepted answer is essentially
bypassing retrieving the list of competitions through the OneToMany relationship and instead creates a new query in a new transaction which eliminates the problem.
Not sure if this is a hack but #Transactional on resolvers doesn't work for me although the logic of doing that does make some sense but I am clearly not understanding the root cause.
For me using AsyncTransactionalExecutionStrategy worked incorrectly with exceptions. E.g. lazy init or app-level exception triggered transaction to rollback-only status. Spring transaction mechanism then threw on rollback-only transaction at the boundary of strategy execute, causing HttpRequestHandlerImpl to return 400 empty response. See https://github.com/graphql-java-kickstart/graphql-java-servlet/issues/250 and https://github.com/graphql-java/graphql-java/issues/1652 for more details.
What worked for me was using Instrumentation to wrap the whole operation in a transaction: https://spectrum.chat/graphql/general/transactional-queries-with-spring~47749680-3bb7-4508-8935-1d20d04d0c6a
I am assuming that whenever you fetch an object of Show, you want all the associated Competition of the Show object.
By default the fetch type for all collections type in an entity is LAZY. You can specify the EAGER type to make sure hibernate fetches the collection.
In your Show class you can change the fetchType to EAGER.
#OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<Competition> competition;
You just need to annotate your resolver classes with #Transactional. Then, entities returned from repositories will be able to lazily fetch data.

Spring Repository issue

I seem to be baffled on how JPA Repositories are suppose to work.
In a nut-shell
#Entity
public class User extends AbstractEntity {
protected final static String FK_NAME = "USER_ID";
#Column(nullable = false)
private String firstName;
#OneToMany(cascade = ALL, fetch = FetchType.LAZY, orphanRemoval = true)
#JoinColumn(name = "userId")
private List<Detail> details = new ArrayList<Detail>();
}
#Entity
public class Detail extends AbstractEntity {
Long userId;
String hello;
}
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByFirstName(#Param("firstName") String firstName);
}
And here is the only controller in the app:
#RestController
public class Home {
#Autowired
UserRepository userRepository;
#Autowired
DetailsRepository loanRepository;
#RequestMapping(value = "")
public HttpEntity home() {
User user = userRepository.findByFirstName("John");
if (user == null) {
user = new User();
user.setFirstName("John");
}
Detail detail = new Detail();
detail.setHello("Hello Msh");
user.getDetails().add(detail);
userRepository.save(user);
return new ResponseEntity("hi", HttpStatus.OK);
}
}
Below a screenshot from debugging session where the app just started and the get request to home() method creates new user, new detail, adds detail to user.
Below example - when the user is saved, the detail entity gets updated
Now on the next request, the old user John is found and has been added a new instance of detail.
The old user has been saved but now the newly created detail does not get updated outside.
How come this only works first time ?
Basically theres so much fail going on so that I would advise you to go a step backwards. If youre wana go the short path of getting a solution for exactly this problem continue reading ;)
First part related to the answer of Jaiwo99:
As I can see in the gradle view of intellij, your using Spring Boot. So it is necessary to place #EnableTransactionManagement on top of your configuration class. Otherwise the #Transacion annotation does not have any effect.
Second part your JPA/Hibernate model mapping. Theres so much bad practise on the net that it is no wonder that most beginners have troubles starting with it.
A correct version could look like (not tested)
#Entity
public class User extends AbstractEntity {
#Column(nullable = false)
private String firstName;
#OneToMany(cascade = ALL, fetch = FetchType.LAZY, orphanRemoval = true, mappedBy="user")
private List<Detail> details = new ArrayList<Detail>();
public void addDetail(Detail detail) {
details.add(detail);
detail.setUser(user);
}
}
#Entity
public class Detail extends AbstractEntity {
#ManyToOne
private User user;
private String hello;
public void setUser(User user){
this.user = user;
}
}
Some general advice related to creating a model mapping:
avoid bi-directional mappings whenever possible
cascade is a decision made on the service level and not at the model level and can have huge drawbacks. So for beginners avoid it.
I have no idea why people like to put JoinColumn, JoinTable and whatever join annotation on top of fields. The only reason to do this is when you have a legacy db (my opinion). When you do not like the names created by your jpa provider, provide a different naming strategy.
I would provide a custom name for the user class, because this is in some databases a reserved word.
Very simple, the first time you saved a new entity outside of hibernate session, the second time, the user object you got is a detached object, by default hibernate will not consider it is changed in this case.
*solution *
Move this logic to another service class, which annotated with #transactional
Or
Annotate your controller with transactional
Or
Override equals and hashCode method on user class may also help

Resources