there is already a similar post. Since this is already older, I hope something has changed since then (How does the FetchMode work in Spring Data JPA)
I would like to run all jpa repository#findById in one select, if the relationship is marked with EAGER. However, spring data ignores the EAGER specification and the FETCH.JOIN annotation from hibernate.
Is there a generic solution that all findById queries are executed in one select?
I wouldn't want to write a separate JPL or EntityGraph for each query. Does anyone know a generic solution?
JpaReposistory
The easiest option would be to write a JpaRepository<T, Id>. This is still a custom repository. However, you do not have to write so much code. You mainly have to write a repository interface for each relevant class and annotate the findById(Long id) method with a graph. The advantage is that if you edit your entity, the repository method will not need any changes because you define the entity graph within the entity class itself.
#Entity
#NamedEntityGraph(name = "Department.detail",
attributeNodes = #NamedAttributeNode("employees"))
public class Department {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(fetch = FetchType.LAZY)
private List<Employee> employees;
// ...
}
public interface DepartmentRepository extends JpaRepository<Department, Long> {
#EntityGraph(value = "Department.detail", type = EntityGraphType.LOAD)
List<Department> findById(Long id);
}
As Spring data ignores the #Fetch(Fetchmode.JOIN) annotation or the information fetch = FetchType.EAGER, you cannot influence the join how you want it to be within the entity itself.
JPQL Query Where You Need It
Another option can be considered as a bad software engineering style: You can call the database queries directly where you need them. This means that you execute the code which you would usually write in the repository.
public ClassWithQueryResults {
#PersistenceContext
private EntityManager entityManager;
public void methodWhereYouNeedYourResults() {
TypedQuery<Department> query = entityManager.createQuery(
"SELECT DISTINCT d FROM Department d LEFT JOIN d.employees e",
Department.class);
List<Department> departments = query.getResultList();
// ...
}
}
Repository With JPQL, Generics and Reflection
Taking the previously suggested idea, you can create a custom repository which is valid for all your entities. The first step would be to create an attribute in your entity class in which you store the attribute which should be fetched.
public class Department extends AbstractEntity {
public static void String ATTRIBUTE_TO_FETCH = "employees";
...
}
With some tweaking, this can be extended to an array/list of all the fields which should be fetched. As this attribute is directly in your entity classes, the chance for any mistakes and future effort is low. Obviously, this attribute should have the same name in all your entities.
The next step would be to create the repository. I provide an example with the findAll() method. You have to pass it only the class name of the entities you want to have and the generics and reflection do the rest. (Consider what you want to do with the exceptions.)
public <T> List<T> findAll(Class<T> tClass)
throws NoSuchFieldException, IllegalAccessException {
String className = tClass.getSimpleName();
String attributeToFetch = (String)
tClass.getDeclaredField("ATTRIBUTE_TO_FETCH").get(null);
String queryString = String.format("SELECT DISTINCT p FROM %s p LEFT JOIN p.%s c",
className, attributeToFetch);
TypedQuery<T> query = entityManager.createQuery(queryString, tClass);
return query.getResultList();
}
Depending on how you want to implement this, the modification/generation of a query through simple manipulation of a String can offer the possibility of SQL injection attacks.
When adding a parent and child with a #OneToOne relation having the same key, I keep on getting this error. It is advised to use #MapsId.
id.IdentifierGenerationException: attempted to assign id from null
one-to-one property
The usual solutions I tried, but that did not solve the problem.
Having both parent and child point to each other
The right Transactional (being springframework's)
Saving using the owner's repo. This does not work because of the violation of a upper restriction.
My parent Entity is (with names having a purpose):
#Entity
#Table(name = "JOHAN_SHARED_SUPPLIER")
public class EntitySharedSupplier {
#Id
#Column(name = "supplier_shared_id")
private Long javaSharedSupplierId;
#Column(name = "supplier_shared_name")
private String javaSharedSupplierName;
#Column(name = "contact_shared_name")
private String javaSharedSupplierContactName;
#OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY,
mappedBy = "supplierSharedRef", orphanRemoval = true)
private EntitySharedProduct javaSharedProduct;
The child is:
#Entity
#Table(name = "JOHAN_SHARED_PRODUCTS")
public class EntitySharedProduct {
#Id
#Column(name = "shared_supplier_id")
private Long javaSupplierSharedId;
#MapsId
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="shared_supplier_id")
private EntitySharedSupplier supplierSharedRef;
#Column(name = "prod_supplier_name")
private String javaSharedProductName;
The repo of the parent is:
#Repository
#Transactional
public interface SupplierSharedRepo extends JpaRepository<EntitySharedSupplier, Long> {
}
The service code:
#Transactional
public void saveSupplier(int sup) {
EntitySharedSupplier supplier = new EntitySharedSupplier();
supplier.setJavaSharedSupplierId((long) sup);
supplier.setJavaSharedSupplierName("SharedSupplier-" + sup);
supplier.setJavaSharedSupplierContactName("SharedSupplier-contact-" + sup);
EntitySharedProduct product = new EntitySharedProduct();
product.setJavaSharedProductName("SharedSupplier-Book-" + sup);
product.setSupplierSharedRef(supplier);
supplier.setJavaSharedProduct( product);
supplierSharedRepo.save(supplier);
}
The solution in this case was a bit difficult.
The solution is by changing the set id/key method in the parent object:
public void setJavaSharedSupplierId(Long javaSupplierId) {
this.javaSharedSupplierId = javaSupplierId;
if( javaSharedProduct != null) {
javaSharedProduct.setJavaSupplierSharedId( javaSupplierId);
}
}
NOTICE:
In most cases you have an automatic id/key generation of the parent at a late stage during the hibernate/jpa save process. At that moment the connection between parent and child is done already.
In this case, the solution works because first the connection is made between parent and child and then the manual id/key of the parent is set.
Till then, I kept on getting either the original error (as shown in the question) or that the id of the id was not set.
The solution needs a small change in the creating of the parent/child objects.
#Transactional
public void saveSupplier(int sup) {
EntitySharedSupplier supplier = new EntitySharedSupplier();
supplier.setJavaSharedSupplierName("SharedSupplier-" + sup);
supplier.setJavaSharedSupplierContactName("SharedSupplier-contact-" + sup);
EntitySharedProduct product = new EntitySharedProduct();
product.setJavaSharedProductName("SharedSupplier-Book-" + sup);
product.setSupplierSharedRef(supplier);
supplier.setJavaSharedProduct( product);
// The next statement has to be put after the previous
supplier.setJavaSharedSupplierId((long) sup);
supplierSharedRepo.save(supplier);
}
The solution works and is easy to understand. I also have software that doesn't need this manual setting of the child id in the parent id. So far, I haven't tracked down why that software works.
If you have a better solution, let me/us know!
I am trying to delete a Child Entity using Child's Repository. I do not want to load the whole Collection of Child in Parent and remove a Child from there because the collection is huge in some cases and can cause memory issues. But after I delete a child when I load the Parent using Parent Repository I get an error that says "Deleted Entity passed to persists".
#Entity
#Table(name="USR")
public class User {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval=true)
private Set<UserApplication> userApplications = new HashSet<UserApplication>();
}
#Entity
#Table(name="USR_2_APL")
public class UserApplication {
#ManyToOne
#JoinColumn(name = "USR_SK")
private User user;
}
#Test
public void testDeleteUserApp() {
List<UserApplication> removedUserApp = userApplicationRepository.findByUserSkAndApplicationSk(1, 5);
userApplicationRepository.delete(removedUserApp);
//*****This is where I see an error that says
//org.springframework.orm.jpa.JpaObjectRetrievalFailureException: deleted entity passed to persist: [UserApplication#<null>]; nested exception is javax.persistence.EntityNotFoundException
userRepository.findByUserLoginName(loginId);
}
I donot know if this will help you but I have something similar and this is what I do to delete the data...
In the repository I have a method like this:-
#Transactional
public Long deleteByByUserSkAndApplicationSk(int userSk, int applicationSk);
The output of the method is the number of rows deleted.
Then you can directly call the method where ever you want to delete.
currently I am wrestling with being able to fetch only the data I need. The findAll() method needs to fetch data dependant on where its getting called.
I do not want to end up writing different methods for each entity graph.
Also, I would avoid calling entitymanagers and forming the (repetitive) queries myself.
Basicly I want to use the build in findAll method, but with the entity graph of my liking. Any chance?
#Entity
#Table(name="complaints")
#NamedEntityGraphs({
#NamedEntityGraph(name="allJoinsButMessages", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre")
}),
#NamedEntityGraph(name="allJoins", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre"),
#NamedAttributeNode("complaintMessages")
}),
#NamedEntityGraph(name="noJoins", attributeNodes = {
})
})
public class Complaint implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private long id;
private Timestamp date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer")
private User customer;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "handling_employee")
private User handling_employee;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="genre")
private Genre genre;
private boolean closed;
#OneToMany(mappedBy = "complaint", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<ComplaintMessage> complaintMessages = new ArrayList<ComplaintMessage>();
//getters and setters
}
And my JPARepository
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
List<Complaint> findByClosed(boolean closed);
#EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
#Override
List<Complaint> findAll(Sort sort);
}
We ran into a similar problem and devised several prospective solutions but there doesn't seem to be an elegant solution for what seems to be a common problem.
1) Prefixes. Data jpa affords several prefixes (find, get, ...) for a method name. One possibility is to use different prefixes with different named graphs. This is the least work but hides the meaning of the method from the developer and has a great deal of potential to cause some non-obvious problems with the wrong entities loading.
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
#EntityGraph(value = "User.membershipYears", type = EntityGraphType.LOAD)
User readByUserId(int id);
}
2) CustomRepository. Another possible solutions is to create custom query methods and inject the EntityManager. This solution gives you the cleanest interface to your repository because you can name your methods something meaningful, but it is a significant amount of complexity to add to your code to provide the solution AND you are manually grabbing the entity manager instead of using Spring magic.
interface UserRepositoryCustom {
public User findUserWithMembershipYearsById(int id);
}
class UserRepositoryImpl implements UserRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public User findUserWithMembershipYearsById(int id) {
User result = null;
List<User> users = em.createQuery("SELECT u FROM users AS u WHERE u.id = :id", User.class)
.setParameter("id", id)
.setHint("javax.persistence.fetchgraph", em.getEntityGraph("User.membershipYears"))
.getResultList();
if(users.size() >= 0) {
result = users.get(0);
}
return result;
}
}
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
}
3) JPQL. Essentially this is just giving up on named entity graphs and using JPQL to handle your joins for you. Non-ideal in my opinion.
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
#Query("SELECT u FROM users WHERE u.id=:id JOIN??????????????????????????")
User findUserWithTags(#Param("id") final int id);
}
We went with option 1 because it is the simplest in implementation but this does mean when we use our repositories we have have to look at the fetch methods to make sure we are using the one with the correct entity graph. Good luck.
Sources:
JPA EntityGraph with different views using Spring
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods
I don't have enough reputation to post all of my sources. Sorry :(
We had the same issue and built a Spring Data JPA extension to solve it :
https://github.com/Cosium/spring-data-jpa-entity-graph
This extension allows to pass named or dynamically built EntityGraph as an argument of any repository method.
With this extension, you would have this method immediatly available:
List<Complaint> findAll(Sort sort, EntityGraph entityGraph);
And be able to call it with an EntityGraph selected at runtime.
Use #EntityGraph together with #Query
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
#EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
#Query("SELECT c FROM Complaint ORDER BY ..")
#Override
List<Complaint> findAllJoinsButMessages();
#EntityGraph(value = "allJoins" , type=EntityGraphType.FETCH)
#Query("SELECT c FROM Complaint ORDER BY ..")
#Override
List<Complaint> findAllJoin();
...
}
Using the #EntityGraph annotation on a derived query is possible, as I found out from This article. The article has the example:
#Repository
public interface ArticleRepository extends JpaRepository<Article,Long> {
#EntityGraph(attributePaths = "topics")
Article findOneWithTopicsById(Long id);
}
But I don't think there's anything special about "with" and you can actually have anything between find and By. I tried these and they work (this code is Kotlin, but the idea is the same):
interface UserRepository : PagingAndSortingRepository<UserModel, Long> {
#EntityGraph(attributePaths = arrayOf("address"))
fun findAnythingGoesHereById(id: Long): Optional<UserModel>
#EntityGraph(attributePaths = arrayOf("address"))
fun findAllAnythingGoesHereBy(pageable: Pageable): Page<UserModel>
}
The article had mentioned the caveat that you can't create a method similar to findAll which will query all records without having a By condition and uses findAllWithTopicsByIdNotNull() as an example. I found that just including By by itself at the end of the name was sufficient: findAllWithTopicsBy(). A little more terse but maybe a little more confusing to read. Using method names which end with just By without any condition may be in danger of breaking in future versions in Spring since it doesn't seem like an intended use of derived queries name.
It looks like the code for parsing derived query names in Spring is here on github. You can look there in case you're curious about what's possible for derived queries repository method names.
These are the spring docs for derived queries.
This was tested with spring-data-commons-2.2.3.RELEASE
EDIT: this doesn't actually work. Ended up having to go with https://github.com/Cosium/spring-data-jpa-entity-graph. The default method LOOKS correct, but doesn't successfully override the annotations.
Using JPA, what I found works is to use a default method, with a different EntityGraph annotation:
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
List<Complaint> findByClosed(boolean closed);
#EntityGraph(attributePaths = {"customer", "genre", "handling_employee" }, type=EntityGraphType.FETCH)
#Override
List<Complaint> findAll(Sort sort);
#EntityGraph(attributePaths = {"customer", "genre", "handling_employee", "messages" }, type=EntityGraphType.FETCH)
default List<Complaint> queryAll(Sort sort){
return findAll(sort);
}
}
You don't have to do any of the re-implementation, and can customize the entity graph using the existing interface.
Can you try create EntiyGraph name with child that you will request and give same name to the find all method.
Ex:
#EntityGraph(value = "fetch.Profile.Address.record", type = EntityGraphType.LOAD)
Employee getProfileAddressRecordById(long id);
For your case:
#NamedEntityGraph(name="all.Customer.handling_employee.genre", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre")
})
method name in repository
#EntityGraph(value = "all.Customer.handling_employee.genre" , type=EntityGraphType.FETCH)
findAllCustomerHandlingEmployeeGenre
This way you can keep track of different findAll methods.
We have the simplest CRUD task with JPA 1.0 and JAX-WS.
Let's say we have an entity Person.
#Entity
public class Person
{
#Id
private String email;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(insertable = false, updatable = false)
private ReadOnly readOnly;
#Column
private String name;
#XmlElement
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
#XmlElement
public Long getReadOnlyValue()
{
return readOnly.getValue();
}
// more get and set methods
}
Here is scenario.
Client make Web Service request to create person. On the server side everything is straightforward.
And it does work as expected.
#Stateless
#WebService
public class PersonService
{
#PersistenceContext(name = "unit-name")
private EntityManager entityManager;
public Person create(Person person)
{
entityManager.persist(person);
return person;
}
}
Now client tries to update person and this is where, as for me, JPA shows its inconsistence.
public Person update(Person person)
{
Person existingPerson = entityManager.find(Person.class, person.getEmail());
// some logic with existingPerson
// ...
// At this point existingPerson.readOnly is not null and it can't be null
// due to the database.
// The field is not updatable.
// Person object has readOnly field equal to null as it was not passed
// via SOAP request.
// And now we do merge.
entityManager.merge(person);
// At this point existingPerson.getReadOnlyValue()
// will throw NullPointerException.
// And it throws during marshalling.
// It is because now existingPerson.readOnly == person.readOnly and thus null.
// But it won't affect database anyhow because of (updatable = false)
return existingPerson;
}
To avoid this problem I need to expose set for readOnly object and do something like this before merge.
Person existingPerson = entityManager.find(Person.class, person.getEmail());
person.setReadOnlyObject(existingPerson.getReadOnlyObject()); // Arghhh!
My questions:
Is it a feature or just
inconsistence?
How do you (or would
you) handle such situations? Please
don't advice me to use DTOs.
Is it a feature or just inconsistence?
I don't know but I'd say that this is the expected behavior with merge. Here is what is happening when calling merge on a entity:
the existing entity gets loaded in the persistence context (if not already there)
the state is copied from object to merge to the loaded entity
the changes made to the loaded entity are saved to the database upon flush
the loaded entity is returned
This works fine with simple case but doesn't if you receive a partially valued object (with some fields or association set to null) to merge: the null fields will be set to null in the database, this might not be what you want.
How do you (or would you) handle such situations? Please don't advice me to use DTOs.
In that case, you should use a "manual merge": load the existing entity using find and update yourself the fields you want to update by copying the new state and let JPA detect the changes and flush them to the database.