I would like to have the roles set fetch lazily. Hibernate fetchType.Lazy doesn't work for this cases, where spring data is used. I 've been trying lots of possibilities like Entitygraph but none of them works on this, or I'm using them wrong.
I have the next classes:
A class User:
#Entity
#JsonRootName(value = "user")
#Table(name = "web_users", schema = "t_dw_comercial")
public class User {
#Id
private int userId;
private String fullName;
#OneToMany(fetch=FetchType.LAZY)
#JoinTable(name="web_users_roles",
joinColumns = {#JoinColumn(name="user_id")},
inverseJoinColumns = {#JoinColumn(name="role_id")}
)
private List<Role> roles;
}
A class Role:
#Entity
#JsonRootName(value = "roles")
#Table(name = "web_roles", schema = "t_dw_comercial")
public class Role {
#Id
private int roleId;
private String roleName;
}
Service:
#Service
public class UserService implements IUserService{
#Autowired
UserRepository repository;
public User findUserByLdapId(String loginName) {
return repository.findUserByLdapId(loginName);
}
}
Repository:
#Repository
public interface UserRepository extends CrudRepository<User, Long>{
#Query("SELECT u FROM User u where u.ldapId= ?1")
public User findUserByLdapId(String loginName);
}
Controller:
#Controller
#RestController
public class UserController {
#Autowired
private IUserService userService;
#CrossOrigin
#RequestMapping(value = "/dashboard", params = {"user"}, method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<User> getUser(#RequestParam(value = "user") String ldapId) {
User user = userService.findUserByLdapId(ldapId);
if(user == null)
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
return new ResponseEntity<>(user, HttpStatus.OK);
};
}
So a json would looks like:
{
"user": {
"userId": 1,
"fullName": "Carolina Ponce",
"roles":[]
}
}
Thanks in advance!
It seems you're asking for two different things: how to fetch #OneToMany association lazily (which should work out of the box) and how to make your JSON look like the above (which has nothing to do with how your JPA entity fetching is configured).
If you serialize your entity using Jackson, by default all the fields will get serialized, including those that are fetched lazily. If the persistence context is still open when you begin to serialize the entities, Jackson will simply trigger lazy loading by accessing the property. As a result, regardless of whether you use FetchType.EAGER or FetchType.LAZY, roles will be included in the result (I assume it is the case, since you'd be getting a LazyInitializationException if the context was closed).
The solution is, simply, to tell Jackson to refrain from serializing roles using #JsonIgnore if you don't want the property in the result.
First of all Hibernate and Spring Data are totally different tools designed for different purposes and they work with each other just fine. You can read more about the differences between both here: What Is the Difference Between Hibernate and Spring Data JPA?
Spring Data has nothing to do with how you fetch entity's related collections, Hibernate, as an implementation of JPA, does.
I assume you indeed fetch your collection lazily and that happens during serialization of your entity so the mechanism works as it should.
I suggest you to create some sort of DTO object that you could return from your controller rather than returning an entity. It seems like you don't want to expose user's roles so creating a DTO without roles may be a good idea. Other solution is to instruct Jackson to ommit roles property during serialization using #JsonIgnore annotation.
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 JPA code with OneToMany relationship. A Customer has a list of Item to check out. However, the code continue to generate StackOverflowError.
Once, I had resolved this one by applying #JsonIgnore while fetching the List<Item> from Customer entity. But even that does not seem to work anymore.
In Customer class:
#OneToMany(mappedBy = "customer", orphanRemoval = true)
#JsonIgnore
private List<Item> items;
In Item class:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CUSTOMER_ID", nullable = false)
private Customer customer;
And CustomerRest class:
#Path("customers")
public class CustomerRest {
#Inject
NewSessionBean newSessionBean;
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Customer> getAllCustomers() {
return newSessionBean.getCustomers();
}
}
Method newSessionBean.getCustomers():
public List<Customer> getCustomers(){
TypedQuery<Customer> q= em.createQuery("select c from Customer c", Customer.class);
return q.getResultList();
}
I expect a nicely formatted JSON message but there is no sign of this. All I get is the java.lang.StackOverflowError on the browser and the Server log generates the following:
Generating incomplete JSON|#]
java.lang.StackOverflowError
java.lang.StackOverflowError at org.eclipse.yasson.internal.serializer.DefaultSerializers.findByCondition(DefaultSerializers.java:130)
It looks like you use Yasson project not Jackson. In that case you should use #JsonbTransient annotation. See documentation:
By default, JSONB ignores properties with a non public access. All
public properties - either public fields or non public fields with
public getters are serialized into JSON text.
Excluding properties can be done with a #JsonbTransient annotation.
Class properties annotated with #JsonbTransient annotation are ignored
by JSON Binding engine. The behavior is different depending on where
#JsonbTransient annotation is placed.
See also:
Circular reference issue with JSON-B
I am using spring framework and hibernate as an ORM tool.
My parent class is like:
#Entity
public class Category {
#Id
#GeneratedValue
#NotNull
private int cid;
private String cname;
#OneToMany(cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
#JoinColumn(name = "cid")
List<Ad> ads;
//getter and setter, constructor
}
And my child class is like:
#Entity
public class Ad {
private int adid;
private String adName;
//getter and setter, constructor
}
My category controller is :
#Controller
public class CategoryController {
#Autowired
SessionFactory sessionFactory;
Session session;
#Transactional
#RequestMapping(value = "categories",method = RequestMethod.GET)
#ResponseBody
public List<Category> getAllCategory()throws SQLException{
session=sessionFactory.getCurrentSession();
return session.createCriteria(Category.class).list();
}
}
When i hit url localhost:8080/categories .I get json data like:
{"cid":"1",cname":"category","ads":[{"adid":"1","adName":"ad"}]}
Here I am getting datas of both parent and the related child table.But how can I get only datas of parent table.Here in this
example, I need data like:
{"cid":"1",cname":"category"}
How can I do this
I saw a nice article which describes exactly your problem.
Json Exclude Property
By configuring Entity using #JsonIgnore and #JsonProperty annotations you can achieve this.
You can try as below
Infinite Recursion with Jackson JSON and Hibernate JPA issue
Basically have to apply exclusion where you need to break the link
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")
In my project, I am using Spring Data JPA and extend the JpaRepository interface for my data fetching class.
OrganizationMaster class :
#Entity
#Table(name="organization_master")
public class OrganizationMaster {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="organization_id")
private int organizationId;
#OneToMany(mappedBy="organizationMaster")
private List<CompanyMaster> companyMasters;
}
CompanyMaster Class:
Entity
#Table(name="company_master")
public class CompanyMaster {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="company_id")
private int companyId;
#ManyToOne
#JoinColumn(name="organization_id")
private OrganizationMaster organizationMaster;
}
My Controller
#RequestMapping(value = "/GetOrganization", method = RequestMethod.GET)
public
#ResponseBody
List<OrganizationMaster> getOrganization(){
return organizationService.getOrganization();
}
OrganizationService:
public interface OrganizationService {
List<OrganizationMaster> getOrganization();
}
OrganizationServiceImpl:
#Service
public class OrganizationServiceImpl implements OrganizationService{
#Autowired
private OrganizationDao organizationDao;
#Override
public List<OrganizationMaster> getOrganization() {
return organizationDao.findAll();
}
}
OrganizationDao Interface:
public interface OrganizationDao extends JpaRepository<OrganizationMaster,Long> {
}
My Output Response is:
[{"organizationId":5,"companyMasters":[{"companyId":29},{"companyId":30}]}]
But my need is
[{"organizationId":5}]
When I am trying to get data from the organization master using findall() method it also fetches data from the company master based on the relationship. How can I achieve lazy fetching (get data only from organization master) using spring data JpaRepository
All XToOne associations are default EAGER. You have a bidirectional relationship, so you should use FetchType.LAZY on your #ManyToOne side.
#ManyToOne(fetch = FetchType.LAZY)
Also if you use any serializer (like json serializer) when it serialize it calls getter methods and it may causes to load lazy items unneccessarly.
Another consideration is while using Lombok, #Data annotation causes to load lazy items without need. So be careful when using Lombok.
So in your case, beacuse of you return entity itself, while serialization it serializes the child too, it causes to load lazly child entity.
You need to return a dto which represents only your parent entity to prevent serialization of child entity. If you call child with getter method, it laods lazly child entity from database.
Take a look for further information associations:
https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
I believe this question is asked before!you can use this annotation:
#OneToMany( fetch = FetchType.LAZY )
read this article for better view in this point:
https://howtodoinjava.com/hibernate/lazy-loading-in-hibernate/