I am trying to retrieve a user's particular order.
This is how I retrieve the user's orders in my OrderController
#GetMapping("/{id}/orders")
public List<Order> findAll(#PathVariable Long id) throws UserNotFoundException {
Optional<User> existingUser = this.userRepository.findById(id);
if (existingUser.isEmpty()) {
throw new UserNotFoundException("User not found");
}
return existingUser.get().getOrders();
}
With the RequestMapping
#RestController
#RequestMapping("/api/users")
public class OrderController {(...)}
This is the OneToMany relationship
User Entity
// ONE TO MANY
#OneToMany(mappedBy = "user")
private List<Order> orders;
Order Entity
// MANY TO ONE
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
private User user;
The UserRepository and OrderRepository interface, both extend JpaRepository
I do manage to retrieve all the user's orders through Postman
I am now wondering, how can I retrieve a user's particular order?
So for instance,
as shown in the image let's say I would only like to retrieve the order with the id of 2, in this particular address :
http://localhost:8070/api/users/1/orders/2
How can I make it please ?
Create an endpoint with
#GetMapping("/{id}/orders/{orderId}")
and return the particular order.
Create an OrderRepository and simply create the
public Order findByIdAndUserId(long orderId,long userId);
interface method for retrieving the given one.
Just a remark: you should validate that the given user is the same as the logged in one. What happen if I send a request to the backend, where I rewrite the user id to someone else's id?
try this,
#GetMapping("/{userId}/orders/{orderId}")
public Order findAll(#PathVariable Long userId, #PathVariable Long orderId) throws Exception {
//your code
}
Obviously you should had a Get mapping:
#GetMapping("/{userId}/orders/{orderId}")
public List<Order> findAll(#PathVariable Long userId, #PathVariable Long orderId) throws UserNotFoundException {
...
}
And for the requesting, you have three options:
Call you this.userRepository.findById(id) and filter after your order.
Create an OrderRepository to limit the request to the order table. But you need to have a reference to the user and you will probably not improve any performance. I would not advise that.
Add a query in your order repository to query the orderId for the userId:
#Repository
public interface UserRepository extends JpaRepository<.., ..> {
#Query("select ... from User user join user.orders ... where ...user = :userId and order = :orderId")
Order getUserOrder(#Param("userId") String userId, #Param("orderId") String orderId);
}
Anyway, You should create a service and inject it in the controller to encapsulate the search/filtering complexity and let you endpoints code clean (#Autowired private OrderService orderService):
#RestController
#RequestMapping("/api/users")
public class OrderController {
#Autowired
private OrderService orderService;
}
and:
#Service
public class OrderService {
}
Related
I've a spring boot application which uses Hibernate as an ORM and DGS framework as the graphql engine. I've been struggling with finding ways to initialize a lazy loaded collection, the proper way. I've the following scenario:
application.properties
# The below has been set to false to get rid of the anti-pattern stuff it introduces
spring.jpa.open-in-view=false
...
#Entity
public class User {
#Id
#GeneratedValue
private UUID id;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Article> articles;
...
}
#Entity
public class Article {
#Id
#GeneratedValue
private UUID id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private User user;
...
}
My User data fetcher looks something like this:
#DgsComponent
public class UserDataFetcher {
#Autowired
private UserService userService;
#DgsQuery
public User getUserById(#InputArgument UUID id) {
return userService.findById(id);
}
...
}
My UserService looks something like this:
#Service
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Override
public User findById(UUID id) {
return userRepository.findById(id).orElseThrow(DgsEntityNotFoundException::new);
}
...
}
Now, I only want to initialize/load my articles collections from the DB when the user asks for it in the graphql query. For that purpose I created a child resolver for my articles which only executes when a user asks for the article in the query. My UserDataFetcher started looking like this:
#DgsComponent
public class UserDataFetcher {
#Autowired
private UserService userService;
#DgsQuery
public User getUserById(#InputArgument UUID id) {
return userService.findById(id);
}
#DgsData(parentType = "User", field = "articles")
public List<Article> getArticle(DgsDataFetchingEnvironment dfe) {
User user = dfe.getSource();
Hibernate.initialize(user.getArticles());
return user.getArticles();
}
...
}
But, the above started throwing exceptions telling me that Hibernate couldn't find an open session for the above request. Which made sense because there wasn't any so I put a #Transactional on top of my child resolver and it started looking like this:
#DgsComponent
public class UserDataFetcher {
#Autowired
private UserService userService;
#DgsQuery
public User getUserById(#InputArgument UUID id) {
return userService.findById(id);
}
#DgsData(parentType = "User", field = "articles")
#Transactional
public List<Article> getArticle(DgsDataFetchingEnvironment dfe) {
User user = dfe.getSource();
Hibernate.initialize(user.getArticles());
return user.getArticles();
}
...
}
However, the above didn't work either. I tried moving this #Transactional into my service layer as well but even then it didn't work and it throwed the same exception. After much deliberation, I founded out that (maybe) Hibernate.initialize(...) only works if I call it in the initial transaction, the one which fetched me my user in the first place. Meaning, it's of no use to me since my use-case is very user-driven. I ONLY want to get this when my user asks for it, and this is always going to be in some other part of my application outside of the parent transaction.
I am looking for solutions other than the following:
Changing the child resolver to something like this:
#DgsData(parentType = "User", field = "articles")
#Transactional
public List<Article> getArticle(DgsDataFetchingEnvironment dfe) {
User user = dfe.getSource();
List<Article> articles = articlesRepository.getArticlesByUserId(user.getUserId);
return articles;
}
I am not in the favor of the above solution since I feel this is under-utilizing the ORM itself by trying to resolve the relation yourself rather than letting hibernate itself do it. (Correct me if I wrong thinking this way)
Changing my User entity to use FetchMode.JOIN.
#Entity
public class User {
#Id
#GeneratedValue
private UUID id;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
#Fetch(FetchMode.JOIN)
private List<Article> articles;
...
}
This is the same as telling hibernate to eagerly load the below collection no matter what. I don't want this either.
Setting spring.jpa.open-in-view=false to spring.jpa.open-in-view=true. Not in the favor of this either since this is just a band aid for LazyInitializationExceptions.
Any other solutions that just makes your forget about LazyInitializationException by keeping the session open throughout the lifecycle of the request.
Please note this answers assumes that Spring Data JPA can be used.
Helpful can be full dynamic usage of EntityGraphs
Entity Graphs give us a possibility to define fetch plans and declare which
relations (attributes) have to be queried from the database.
According to the documentation
You can do something similar to this
productRepository.findById(1L, EntityGraphUtils.fromAttributePaths(“article, “comments”));
And pass all necessary params (relations) based on user selection to the EntityGraphUtils.fromAttributePaths method.
This give us possibility to fetch only necessary data.
Additional resources:
Sample project
Spring Blog mentioned this extension
JPA EntityGraph
EntityGraph
Another workaround I've used is to skip any child resolver and just load additional entities conditionally in the base resolver.
#DgsQuery
public User getUserById(#InputArgument UUID id) {
var user = userService.findById(id);
if (dfe.getSelectionSet().contains("articles") {
Hibernate.initialize(user.getArticles());
}
return user;
}
I have a User object, and a Ticket object that have a ManyToMAny relationship
class User{
private Long id;
#ManyToMany
private Set<Ticket> tickets;
}
class Ticket{
#ManyToMany
private Set<User> users;
}
Obviously this is a very simplified pseudo-like version of the code, but what would i name the method in my JPA Repository, to get all of the tickets that have a user with the specified ID in it? Is this possible, or should I make a custom query?
You can write 2 different named queries:
public interface TicketRepository extends JpaRepository<Ticket, Long> {
List<Ticket> findAllByUsers(User user);
List<Ticket> findAllByUsersIdIn(List<Long> userIds);
}
Method findAllByUsers(..) takes User object to search and return results, method findAllByUsersIdIn(..) takes user ids to search and return results.
I am designing an API using Spring boot for some social network backend. My current model looks like this:
public class User {
private long id;
private String handle;
private String name;
private List<User> followers;
private List<User> following;
// Getters setters etc
Now, I have created DTO which closely resembles above structure. My problem is that sometimes I want to return exactly what's above (which is fine) but sometimes, I don't want that.
For example when someone is only interested in finding followers of user, I don't want to include followers and following (I am simply interested in id, handle and name so computing followers and following for all of those users would be an incredible waste of resources).
In my current implementation those fields are returned with null values, which I don't think is a great idea. Should I create a separate DTO without those lists with just id, handle and name? Or is there more elegant way to do it?
It is a controversial issue. If you don't want to create separate dto there are several ways to do it. It depends on what data access approach you are going to use:
Using Spring Data JPA it is possible to return an entity in projection. You just need to add an additional constructor to your entity:
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select new User(u.id,u.name) from User u")
List<User> findAllUserItems();
}
Or same using JPA EntityManger:
public List<User> findAllUserItems() {
return entityManager.createQuery("select new User(u.id,u.name) from User u", User.class)
.getResultList();
}
If you wonder about unnecessary null fields and you are using Jackson, it is possible to configure to ignore null fields. For Spring Boot:
spring.jackson.default-property-inclusion=non_null
Or with Java config:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
return builder;
}
Or for not Spring Boot project:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(converter());
}
#Bean
public HttpMessageConverter converter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
Also, if you going to use Hibernate Session. To map directly to dto you may use AliasToBeanResultTransformer:
public List<UserDto> findAllUserItems() {
return session.createQuery("select u.id as id,u.name as name from User u")
.setResultTransformer(Transformers.aliasToBean(UserDto.class))
.list();
}
Suppose you can try like this.
#Entity
public class User implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#ManyToOne
private User parent;
#OneToMany(mappedBy="parent")
private List<User> followers;
#OneToMany(mappedBy="parent")
private List<User> following;
// Getters, Setters etc....
}
Also this post may be helpful.
I have two entities, User and Operation and both entities have a join among them:
#Entity
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
#Basic
private String username;
private String password;
//Getters and Setters
}
#Entity
public class Operation implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
#ManyToOne
#JoinColumn(name = "user_id")
User user;
//Getters and Setters
}
Both Entities has a Repository too.
In my context the User entity is loadded in Session scope (HttpSession) when user (operator) has been logged.
For each operation of user on system the app register that operation throug the Operation Repository.
My question is: How can I set User entity (getting on session) to operation before the register in Database?
Is possible override the Repository method?
EDIT 1:
The operation is saved through the web interface using HTTP POST method. I need to keep using the URI to save. Like:
URI: http: // localhost: 9874 / operations
DATA: { "name": "operation-name" }
Thanks!
You can create a pre save event handler in which you can set the association: you can then make a standard Spring Data Rest post to http://localhost:9874/operations and there is no need for a custom repository or controller.
http://docs.spring.io/spring-data/rest/docs/current/reference/html/#_writing_an_annotated_handler
#RepositoryEventHandler
public class OperationEventHandler {
#HandleBeforeSave
public void handleOperationSave(Operation operation) {
}
}
You say the user is stored in the session. I take it then you are not using Spring Security? If you are then you can get the current user using a static call:
SecurityContextHolder.getContext().getAuthentication();
otherwise you would need try and wire the HttpServletRequest to your event handler or use the static wrapper call as outlined in the answers to these questions:
Spring: how do I inject an HttpServletRequest into a request-scoped bean?
From this you can get the HttpSession.
The following shows wiring in the HttpServletRequest in exactly this scenario
Spring Data Rest - How to receive Headers in #RepositoryEventHandler
so your handler looks something like:
#RepositoryEventHandler
public class OperationEventHandler {
#Autowired
private HttPServletRequest request;
#HandleBeforeSave
public void handleOperationSave(Operation operation) {
User user = (User)request.getSession().getAttribute("userKey");
operation.setUser(user);
}
}
Create a Custom Repository interface and write a implementation for that. for example.
public interface OperationRepositoryCustom {
<T extends Operation> T saveCustomized(Operation operation);
}
Your implementation class would look like this.
public class OperationRepositoryImpl implements OperationRepositoryCustom{
//You can autowire OperationRepository and other dependencies
#Autowired
OperationRepository operationRepository;
#Override
public Operation saveCustomized(Operation operation) {
//put your code to update operation with user and save it by calling operationRepository.save().
}
}
be aware of the naming convention, that Your custom implementation needs to have the same name like your repository + Impl. So if your repository interface is called OperationRepository, your Custom repository interface should be OperationRepositoryCustom and impl should be named OperationRepositoryImpl
Hope it helps
Premise:
I chose to do this because I might end up having a few thousand schemas, each having (amongst others) 1 table with a few million entries. The alternative was having (amongst others) one table with a few billion entries in one schema.
The best way to elaborate on this question is providing a simple example. Consider the following:
User.java
#Entity(name = "user")
public class User {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#Column(name = "username")
private String username;
// getters and setters...
}
UserDao.java
#Repository
public interface UserDao extends CrudRepository<User, Long> {}
UserService.java
public interface UserService {
User getUser(Long id);
}
UserServiceBean.java
#Transactional
#Service
public class UserServiceBean implements UserService {
#Autowired
private UserDao dao;
#Override
public User getUser(Long id) {
return dao.findOne(id);
}
}
UserController.java
#RestController
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(
value = "/api/users/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(
#PathVariable("id") Long id) {
User user = userService.getUser(id);
return new ResponseEntity<User>(user, HttpStatus.OK);
}
}
I would like to extend to the following functionality: supplying another ID in the URL in order to return user data from a different table.
UserController.java
...
#RequestMapping(
value = "/api/users/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(
#PathVariable("id") Long id,
#RequestParam(value = "tlbid") Long tblId) {
User user = userService.getUser(id, tblId);
return new ResponseEntity<User>(user, HttpStatus.OK);
}
Now the UserService will decode that ID into something that could be useful for spring in order to get the data from a different table.
UserServiceBean.java
...
public User getUser(Long id, Long tblId) {
Object o = doMagic(tblId);
// What should 'o' be and how could I use this?
}
All the tables have the same structure and names but different entries. The tables have to be on a different database, or in the same database but on a different schema.
I would like to know either:
a) How can I have one database connection and specify a different schema for every request.
b) How to create new database connections when necessary (I would maintain them for further requests), and specify on which connection should the request be made each time.
c) My premises are wrong and having billions of entries in a table and high concurrency does not significantly slow down query speeds.
It sounds like you're describing a multi-tenant solution. See the Hibernate documentation for a longer description and a few options for how you could partition your data.
Note: we are trying to implement the schema-based multi-tenant approach at the moment :)
In case if you are using hibernate entity class then you can always use different schemas for same datasource provided that the other schemas are accessible for the particular user mapped in Datasource.
you can use schema attribute of Table annotation in your entity class. Use the below syntax for using different schema
#Table(name="TABLE_NAME",schema="SCHEMA2")