I am developing an application using Spring Boot using JPA.
In the application I am exposing a rest API. I do not want to use Spring data rest as I want to have full control of the data.
I am not able to figure out how to use EntityGraph dynamically.
Suppose I have following model taken from here
#Entity
class Product {
#ManyToMany
Set<Tag> tags;
// other properties omitted
}
interface ProductRepository extends Repository<Customer, Long> {
#EntityGraph(attributePaths = {"tags"})
Product findOneById(Long id);
}
I have following rest link to access Product
http://localhost:8090/product/1
It returns to me a product with id 1
Questions:
Will it by default fetch tags as we have mentioned #EntityGraph?
If yes, then can this be configured on demand? Say, if in the query
string I have include=tags, then only I want to fetch product with
its tags.
I found this article but not sure how this can be of help.
The definition of the EntityGraph in the Spring Data JPA Repository is static. If you want to have it dynamic you need to do this programatically like in the page you linked to:
EntityGraph<Product> graph = this.em.createEntityGraph(Product.class);
graph.addAttributeNodes("tags"); //here you can add or not the tags
Map<String, Object> hints = new HashMap<String, Object>();
hints.put("javax.persistence.loadgraph", graph);
this.em.find(Product.class, orderId, hints);
Also you can define the method with the EntityGraph in your JPA Repository.
interface ProductRepository extends Repository<Product, Long> {
#EntityGraph(attributePaths = {"tags"})
#Query("SELECT p FROM Product p WHERE p.id=:id")
Product findOneByIdWithEntityGraphTags(#Param("id") Long id);
}
And then have a method in your service which uses this method with the EntityGraph or the built in findOne(T id) without the EntityGraph:
Product findOneById(Long id, boolean withTags){
if(withTags){
return productRepository.findOneByIdWithEntityGraphTags(id);
} else {
return productRepository.findOne(id);
}
}
You can choose EntityGraph at runtime, by using Spring Data JPA EntityGraph.
Setup quite simple:
Add: implementation 'com.cosium.spring.data:spring-data-jpa-entity-graph:2.0.7' to build.gradle
Add: #EnableJpaRepositories(repositoryFactoryBeanClass = EntityGraphJpaRepositoryFactoryBean.class) bellow #SpringBootApplication
Now, you can choose the best EntityGraph at runtime. Example (this is the example from Spring Data JPA EntityGraph):
// This will apply 'Product.brand' named EntityGraph to findByLabel
productRepository.findByLabel("foo", EntityGraphs.named("Product.brand"));
// This will apply 'Product.supplier' named EntityGraph to findByLabel
productRepository.findByLabel("foo", EntityGraphs.named("Product.supplier"));
// This will apply 'supplier' attribute paths EntityGraph (don't need to define named EntityGraph) to findByLabel
productRepository.findByLabel("foo", EntityGraphUtils.fromAttributePaths("supplier"));
Please read the document for more information.
you can do this in the repository:
interface ProductRepository extends Repository<Product, Long> {
Product findOneById(Long id);
#EntityGraph(attributePaths = {"tags"})
Product findOneWithTagsById(Long id);
}
and create a service method as Robert Niestroj proposed.
You can add an Entity graph as bellow, make sure the entity product class has a relation with the tag class.
#EntityGraph(attributePaths = {
"tags"
})
#Query( value = "select product from product)
List<Product> findAllProduct();
Related
I have an entity with the following relationship in my Store entity:
#OneToMany(mappedBy="computer")
#JsonProperty("computers")
public Set<Computer> computers;
In my StoreService:
List<Computers> computers;
Pageable paging = PageRequest.of(page, size);
Page<Computers> pageTuts;
pageTuts = storeRepository.findAllComputers(paging);
computers = pageTuts.getContent();
Map<String, Object> response = new HashMap<>();
response.put("computers", computers);
response.put("current_page", pageTuts.getNumber());
response.put("total_items", pageTuts.getTotalElements());
response.put("total_pages", pageTuts.getTotalPages());
Now I need to somehow in my StoreRepository interface query for all computers that are relational to that entity. How can I do that? I thought I could just add a name like this:
Page<Computers> findAllComputers(Pageable pageable);
Any ideas how to solve this? Do I have to write a custom Query or something? I think this operation should be some kind of standard so it's hard to think that I would need that.
You should create ComputerRepository and add this kind of method to your repository class.
public interface ComputerRepository extends PagingAndSortingRepository<Computer, Integer> {
Page<Computers> findByStore(Store store, Pageable pageable);
}
Here is quick explanation of Pagination and Sorting.
https://www.baeldung.com/spring-data-jpa-pagination-sorting
In Spring Data JPA we can define a repository interface extending Repository and write a custom method.
If this method follows special syntax, Spring Data will generate the method body automatically.
For example (from the documentation):
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}
Is there a way to customize the method generation code to introduce new keywords into the syntax?
For example:
Person findExactlyOneById(Long id);
This method would either return the entity or throw a custom exception.
I know I can customize specific repositories as well as the base repository and achieve the effect from the above example, but I'm specifically asking for the automatic method of body generation.
Is there an extension point designed in the framework? Or is the only option to change the source code?
In your case, you can always use CrudRepository.findById(Long id) or JpaRepository.getOne(Long id).
I would suggest inheriting from the JpaRepository class because all types of repositories are included.
You can set nativeQuery = true in the #Query annotation from a Repository class like this:
public static final String FIND_PROJECTS = "SELECT projectId, projectName FROM projects";
#Query(value = FIND_PROJECTS, nativeQuery = true)
public List<Object[]> findProjects();
It's probably worth looking at the Spring data docs as well.
Some more example
1.
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
User findByEmailAddress(String emailAddress);
}
I am using spring boot with spring data jpa and postgre. I have "item" entity that has price, quantity, auto generated int id and order that it belongs to.
I've searched how to edit that entity changing its price and quantity only, without making new entity and the only answer I got is to get the entity from the db and set each property to the new one then save it. But if i have 6 other properties except price and quantity that means in the update method i will set a property 8 times and this seems to me like way too much boilerplate code for spring. My question is: Is there better/default way to do that?
You can provide a copy constructor:
public Item(Item item) {
this(item.price, item.quantity);
}
or use org.springframework.beans.BeanUtils method:
BeanUtils.copyProperties(sourceItem, targetItem, "id");
Then in controller:
#RestController
#RequestMapping("/items")
public class ItemController {
#Autoware
private ItemRepo repo;
#PutMapping("/{id}")
public ResponseEntity<?> update(#PathVariable("id") Item targetItem, #RequestBody Item sourceItem) {
BeanUtils.copyProperties(sourceItem, targetItem, "id");
return ResponseEntity.ok(repo.save(targetItem));
}
}
No, you don't need to set anything for 8 times. If you want to change price and quantity only, just change those two. Put it in a #Transactional method:
#Transactional
public void updateItem(Item item){
// ....
// EntityManager em;
// ....
// Get 'item' into 'managed' state
if(!em.contains(item)){
item = em.merge(item);
}
item.price = newPrice;
item.quantity = newQuantity;
// You don't even need to call save(), JPA provider/Hibernate will do it automatically.
}
This example will generate a SELECT and a UPDATE query. And that's all.
Try using #Query annotation and define your update statement
#Modifying
#Transactional
#Query("update Site site set site.name=:name where site.id=:id")
void updateJustNameById(#Param("id")Long id, #Param("name")String name);
You should use spring data rest which handles all of this by itself. you just have to call a patch request at the specified URL and provide the changed entity properties. if you have some knowledge of spring data rest have a look at https://github.com/ArslanAnjum/angularSpringApi.
Just use this #DynamicUpdate in your Entity class
#DynamicUpdate
public class Item{
}
How can one configure their JPA Entities to not fetch related entities unless a certain execution parameter is provided.
According to Spring's documentation, 4.3.9. Configuring Fetch- and LoadGraphs, you need to use the #EntityGraph annotation to specify fetch policy for queries, however this doesn't let me decide at runtime whether I want to load those entities.
I'm okay with getting the child entities in a separate query, but in order to do that I would need to configure my repository or entities to not retrieve any children. Unfortunately, I cannot seem to find any strategies on how to do this. FetchPolicy is ignored, and EntityGraph is only helpful when specifying which entities I want to eagerly retrieve.
For example, assume Account is the parent and Contact is the child, and an Account can have many Contacts.
I want to be able to do this:
if(fetchPolicy.contains("contacts")){
account.setContacts(contactRepository.findByAccountId(account.getAccountId());
}
The problem is spring-data eagerly fetches the contacts anyways.
The Account Entity class looks like this:
#Entity
#Table(name = "accounts")
public class Account
{
protected String accountId;
protected Collection<Contact> contacts;
#OneToMany
//#OneToMany(fetch=FetchType.LAZY) --> doesn't work, Spring Repositories ignore this
#JoinColumn(name="account_id", referencedColumnName="account_id")
public Collection<Contact> getContacts()
{
return contacts;
}
//getters & setters
}
The AccountRepository class looks like this:
public interface AccountRepository extends JpaRepository<Account, String>
{
//#EntityGraph ... <-- has type= LOAD or FETCH, but neither can help me prevent retrieval
Account findOne(String id);
}
The lazy fetch should be working properly if no methods of object resulted from the getContacts() is called.
If you prefer more manual work, and really want to have control over this (maybe more contexts depending on the use case). I would suggest you to remove contacts from the account entity, and maps the account in the contacts instead. One way to tell hibernate to ignore that field is to map it using the #Transient annotation.
#Entity
#Table(name = "accounts")
public class Account
{
protected String accountId;
protected Collection<Contact> contacts;
#Transient
public Collection<Contact> getContacts()
{
return contacts;
}
//getters & setters
}
Then in your service class, you could do something like:
public Account getAccountById(int accountId, Set<String> fetchPolicy) {
Account account = accountRepository.findOne(accountId);
if(fetchPolicy.contains("contacts")){
account.setContacts(contactRepository.findByAccountId(account.getAccountId());
}
return account;
}
Hope this is what you are looking for. Btw, the code is untested, so you should probably check again.
You can use #Transactional for that.
For that you need to fetch you account entity Lazily.
#Transactional Annotations should be placed around all operations that are inseparable.
Write method in your service layer which is accepting one flag to fetch contacts eagerly.
#Transactional
public Account getAccount(String id, boolean fetchEagerly){
Account account = accountRepository.findOne(id);
//If you want to fetch contact then send fetchEagerly as true
if(fetchEagerly){
//Here fetching contacts eagerly
Object object = account.getContacts().size();
}
}
#Transactional is a Service that can make multiple call in single transaction
without closing connection with end point.
Hope you find this useful. :)
For more details refer this link
Please find an example which runs with JPA 2.1.
Set the attribute(s) you only want to load (with attributeNodes list) :
Your entity with Entity graph annotations :
#Entity
#NamedEntityGraph(name = "accountGraph", attributeNodes = {
#NamedAttributeNode("accountId")})
#Table(name = "accounts")
public class Account {
protected String accountId;
protected Collection<Contact> contacts;
#OneToMany(fetch=FetchType.LAZY)
#JoinColumn(name="account_id", referencedColumnName="account_id")
public Collection<Contact> getContacts()
{
return contacts;
}
}
Your custom interface :
public interface AccountRepository extends JpaRepository<Account, String> {
#EntityGraph("accountGraph")
Account findOne(String id);
}
Only the "accountId" property will be loaded eagerly. All others properties will be loaded lazily on access.
Spring data does not ignore fetch=FetchType.Lazy.
My problem was that I was using dozer-mapping to covert my entities to graphs. Evidently dozer calls the getters and setters to map two objects, so I needed to add a custom field mapper configuration to ignore PersistentCollections...
GlobalCustomFieldMapper.java:
public class GlobalCustomFieldMapper implements CustomFieldMapper
{
public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping)
{
if (!(sourceFieldValue instanceof PersistentCollection)) {
// Allow dozer to map as normal
return;
}
if (((PersistentCollectiosourceFieldValue).wasInitialized()) {
// Allow dozer to map as normal
return false;
}
// Set destination to null, and tell dozer that the field is mapped
destination = null;
return true;
}
}
If you are trying to send the resultset of your entities to a client, I recommend you use data transfer objects(DTO) instead of the entities. You can directly create a DTO within the HQL/JPQL.
For example
"select new com.test.MyTableDto(my.id, my.name) from MyTable my"
and if you want to pass the child
"select new com.test.MyTableDto(my.id, my.name, my.child) from MyTable my"
That way you have a full control of what is being created and passed to client.
I would like to expose new endpoints for my repository which also extends RevisionRepository.
#RepositoryRestResource(collectionResourceRel = "persons", itemResourceRel = "person", path = "persons")
public interface PersonRepository extends PagingAndSortingRepository<PersonEntity, Long>, RevisionRepository<PersonEntity, Long, Integer> {
Revision<Integer, PersonEntity> findLastChangeRevision(#Param("id") Long id);
Revisions<Integer, PersonEntity> findRevisions(#Param("id") Long id);
Page<Revision<Integer, PersonEntity>> findRevisions(#Param("id") Long id, Pageable pageable);
PersonEntity findByName(#Param("name") String name);
}
My issue right now, is that these new methods are not exposed as urls (findLastChangeRevision, findRevisions) and only findByName is under the search url. I am currently not very particular with regards to the actual url form, as long as it works.
The only option I know right now is to
Separate the revision repositories
Create a new controller that maps to "/", to replace the one created by Spring Data Rest, and add all the repository links manually. One of my issues here is that my links will be hardcoded (unlike when linking to Controllers), and the paths will be relative-- not necessarily bad, but will make everything inconsistent.
Add links to "/" that maps to the revision repositories
I have a lot of reservations with my option above. I am not sure how to proceed.
You have made a mistake in your method names. Find methods in the Repository class should be findByxxxxxx not findxxxxx
That seems to be the problem with your code.
#RepositoryRestResource(collectionResourceRel = "persons", itemResourceRel = "person", path = "persons")
public interface PersonRepository extends PagingAndSortingRepository<PersonEntity, Long>, RevisionRepository<PersonEntity, Long, Integer> {
Revision<Integer, PersonEntity> findByLastChangeRevision(#Param("id") Long id);
Revisions<Integer, PersonEntity> findByRevisions(#Param("id") Long id);
Page<Revision<Integer, PersonEntity>> findByRevisions(#Param("id") Long id, Pageable pageable);
PersonEntity findByName(#Param("name") String name);
}