How to load only specified attributes in subgraph in an #NamedEntityGraph - java

I want to load an UserReference object from the database, but from the verifier attribute I want to load only the id, firstName and lastName, so that the userReference would look something like this:
{
"id": 1,
"company": "company1",
"companyContactName": "some name",
"companyPosition": "programmer",
"referenceDate": "02/04/2005",
"verifier": {
"id":1
"firstName": "Jane",
"lastName": "Smith"
"email":null,
"username":null,
"office:null,
"department":null
}
}
I used an entity graph for the UserReference class, but the one I used loads all the information that the user has, including the email, username, office and department.
Is there any way to specify something like EntityGraphType.FETCH to the subgraph, so that it will load only the id, firstName and lastName for the verifier?
This is my UserReferenceRepository:
public interface UserReferenceRepository extends JpaRepository<UserReference, Long>{
#EntityGraph(value = "userReferenceGraph" , type = EntityGraphType.FETCH )
UserReference findOne(Long id);
}
The UserReference class:
#Getter
#Setter
#EqualsAndHashCode (exclude = {"id", "verifier"})
#ToString(exclude = {"id"})
#Entity
#NamedEntityGraphs({
#NamedEntityGraph(
name = "userReferenceGraph",
attributeNodes = {
#NamedAttributeNode(value = "verifier", subgraph = "verifierGraph")
},
subgraphs = {
#NamedSubgraph(
name = "verifierGraph",
type = User.class,
attributeNodes = {
#NamedAttributeNode(value = "id"),
#NamedAttributeNode(value = "firstName"),
#NamedAttributeNode(value = "lastName")})})
})
public class UserReference {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "user_id", foreignKey = #ForeignKey (name = "FK_UserReference_UserHRDetails_user_id"))
#JsonIgnore
private UserHRDetails hrDetails;
private String company;
private String companyContactName;
private String companyPosition;
private Date referenceDate;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "verifier_id")
private User verifier;
}
and User:
#Getter #Setter
#EqualsAndHashCode(exclude = {"id", "department", "company", "authorities", "hrDetails"})
#ToString(exclude = {"password"})
#Entity
#AllArgsConstructor
#Builder
public class User implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Access(value = AccessType.PROPERTY)
private Long id;
#Size(max = 50)
#Column(name = "first_name", length = 50)
private String firstName;
#Size(max = 50)
#Column(name = "last_name", length = 50)
private String lastName;
#Column(length = 100, unique = true, nullable = false)
private String email;
#Column(length = 50, unique = true, nullable = false)
private String username;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "department_id")
private Department department;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "office_id")
private Office office;
}

I suppose you are using Jackson for generating JSON. In this case this is a battle Jackson vs. Entity Graph and former has no chance to win this battle. Entity Graph is just a hint for building SQL query and you can only tell Hibernate to not load some attributes. Hibernate still does not support Entity Graphs when it loads basic Entity fields, see https://hibernate.atlassian.net/browse/HHH-9270. But main problem is that Jackson will call every getter in your Entity during JSON generation and Hibernate will lazy load them not taking into consideration your Entity Graph. I can propose only #JsonIgnore usage, but that may be not as flexible as you need.

I've met the same problem, and i see 2 ways to solve it:
FAST:
You can make some #PostLoad action in your entity and nullate that field you don't need.
#PostLoad
private void postLoad() {
if (verifier != null) {
verifier.email = null;
verifier.office = null;
verifier.department = null;
}
}
CORRECT:
Another way is to protect your entities with converting them to DTOs. Create separate POJOs and convert your User and UserReference to that DTO POJO classes. There you definitely will have more control over your response.

Related

How do POST with JSON with OneToMany?

I am using SpringBoot, so lets say first, I want to make a Country, and after doing that POST with JSON how can I do other POST to create a City and adding it to the Country created?
Or I cant do it with JSON?
And idk if is a good idea having the FK pointing the name instead of the ID, in my head it works the same bc is an unique key, right?
Thanks!
Country code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "country_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class Country implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#OneToMany(mappedBy = "country", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<City> cities = new HashSet<>();
}
City code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "city_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class City implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "country_name", referencedColumnName = "name", nullable = false,
foreignKey=#ForeignKey(name = "FK_country_city"))
private Country country;
#OneToMany(mappedBy = "city", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Neighborhood> neighborhoods = new HashSet<>();
}
Neiborhood code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "neighborhood_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class Neighborhood implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#NonNull
private String neighborhoodType;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "city_name", referencedColumnName = "name", nullable = false,
foreignKey=#ForeignKey(name = "FK_city_neighborhood"))
private City city;
}
So first you need to do the POST request to create Country object:
{
"name": "USA",
"cities": []
}
Second you need to do the POST request to create the City object and put the field country with the Primary Key (PK):
{
"name": "Huston",
"country": 1,
"neighborhoods": []
}
That's pretty much it actually.

OneToMany relationship does not store entity field

When saving my entities, child entities that work through the #OneToMany relationship are not saved to their tables. I can’t understand what’s the matter.
Employee:
#Entity
#Table(name = "EMPLOYEE", schema = PUBLIC)
public class Employee {
private String name;
private String lastname;
#OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, orphanRemoval = true)
List<EmployeePhoneNumber> employeePhoneNumbers = new ArrayList<>();
}
EmployeePhoneNumber:
#Entity
#Table(name = "EMPLOYEE_PHONES", schema = PUBLIC)
public class EmployeePhoneNumber {
#Id
#SequenceGenerator(allocationSize = 1, name = "SEQ_EMPLOYEE_PHONES", schema = PUBLIC,
sequenceName = "EMPLOYEE_PHONES_ID_SEQ")
#GeneratedValue(generator = "SEQ_EMPLOYEE_PHONES", strategy = GenerationType.SEQUENCE)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#ManyToOne
#JoinColumn(name = "employee_id", referencedColumnName = "id",
nullable = false, insertable = false, updatable = false)
private Employee employee;
#Column(name = "PHONE_NUMBER", unique = true, nullable = false)
private String phoneNumber;
#Enumerated(EnumType.STRING)
#Column(name = "NUMBER_TYPE", nullable = false)
private PhoneNumberType phoneNumberType;
}
How I set those fields and then save the entity:
EmployeePhoneNumber workPhone = new EmployeePhoneNumber();
workPhone.setPhoneNumber(workPhone);
workPhone.setPhoneNumberType(PhoneNumberType.WORK_PHONE);
EmployeePhoneNumber mobilePhone = new EmployeePhoneNumber();
mobilePhone.setPhoneNumber(mobilePhone);
mobilePhone.setPhoneNumberType(PhoneNumberType.MOBILE_PHONE);
EmployeePhoneNumber corporatePhone = new EmployeePhoneNumber();
corporatePhone.setPhoneNumber(corporatePhoneNumber);
corporatePhone.setPhoneNumberType(PhoneNumberType.CORPORATE_PHONE);
List<EmployeePhoneNumber> employeePhoneNumbers = employee.getEmployeePhoneNumbers();
employeePhoneNumbers.add(workPhone);
employeePhoneNumbers.add(mobilePhone);
employeePhoneNumbers.add(corporatePhone);
employee.setEmployeePhoneNumbers(employeePhoneNumbers);
employeeRepository.save(employee);
Upon completion of the method, I do not have a single error, everything works out correctly, only the tables are not filled - why?
You must also set the Employee reference in EmployeePhoneNumber because Hibernate will use this to save it.
workPhone.setEmployee(employee);
mobilePhone.setEmployee(employee);
corporatePhone.setEmployee(employee);
The best solution would be to create an addEmployeePhoneNumber method on the Employee like this:
public void addEmployeePhoneNumber(EmployeePhoneNumber phoneNumber) {
phoneNumber.setEmployee(this);
employeePhoneNumbers.add(phoneNumber);
}
That way you will not forget to set both sides of the relationship.

how to find id of foreign key in hibernate

I have two entities viz:
State
#Entity
#Table(name = "State")
public class StateEntity {
#Column(name = "id", length = 36, nullable = false, unique = true)
private String id;
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn(name = "InsurerId", nullable = false)
private InsurerEntity insurer;
#Column(name ="StateName", length = 50, nullable = false)
private String stateName;
//getters and setters
}
Insurer
#Entity
#Table(name = "Insurer")
public class InsurerEntity {
#Column(name = "InsurerId", length = 36, nullable = false, unique = true)
private String insurerId;
#Column(name = "InsurerName", length = 100, nullable = true)
private String insurerName;
#OneToMany(mappedBy = "state", fetch = FetchType.LAZY)
private List<StateEntity> stateEntityList;
//getters and setters
}
the insurer's id gets saved in state database and I want to retrieve it using hibernate query but I cant't seem to find the solution for that
How to write this query SELECT InsurerId FROM State; in Hibernate query using CriteriaBuilder, CriteriaQuery and Root..
If you want to select all Insurers's Ids for all states:
String selectionQuery = "SELECT s.insurer.insurerId FROM State s";
List<String> insurersIds = session.createQuery(selectionQuery).list();
If you want to select the Insurer's Id of a certain state:
String selectionQuery = "SELECT s.insurer.insurerId FROM State s WHERE s.id = :stateId";
String insurerId = (String) session.createQuery(selectionQuery).setParameter("stateId", stateId).getSingleResult(); //This should be placed in a try/catch block to handle org.hibernate.NonUniqueResultException
Edit:
You should update your Insurer entity as Prasad wrote in his answer.
for this you have to map both the class as in put #oneToMany annotation in class InsurerEntity as well
#OneToMany(fetch = FetchType.LAZY,mappedBy="StateEntity", cascade = CascadeType.ALL)
private List< StateEntity > StateEntitys;
and when you fetch states you will also get object of InsurerEntity in it from where you can access it with the getter

How to convert a SQL query to Spring JPA query

I have a SQL query like this:
"Select UIProfileID from UserTable where UPPER(UserID) = UPPER('?1')".
I want to convert it to Spring JPA.
I want to write getUIProfileId() and return Integer. But I don't know how to implement. Because User table doesn't have UIProfileId column that it was joined from UIProfileTable table. Please help me solve it.
Currently, I have tables:
User.java
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Table(name = "UserTable")
public class User {
#Column(name = "UserID", length = 32, nullable = false)
#Id
private String name;
#ManyToOne
#JoinColumn(name = "DomainID", nullable = false)
private Domain domain;
#Column(name = "Password", length = 32, nullable = false)
private String password;
#ManyToOne
#JoinColumn(name = "UIProfileID", nullable = false)
private UIProfile uiProfile;
#Column(name = "ResPerpage", nullable = false)
private Integer resperpage;
#Column(name = "DefaultTab")
private Integer defaulttab;
#ManyToOne
#JoinColumn(name = "AdminProfile")
private AdminProfiles adminProfile;
#Column(name = "LanguageId")
private Integer languageId;
}
UIProfile.java
#Entity
#Getter
#Setter
#Table(name = "UIProfileTable")
public class UIProfile implements Serializable {
#Id
#Column(name = "UIProfileID", length = 11, nullable = false)
private Integer id;
#Column(name = "UIProfileName", length = 32, nullable = false)
private String name;
#OneToMany(mappedBy = "id.uiProfile")
private List<UIProfileTopLevel> topLevels;
}
UserRepository.java
public interface UserRepository extends Repository<User, String> {
Optional<User> findOne(String name);
#Query("Select UIProfileID from User where UPPER(UserID) = UPPER('admin')")
Integer getUIProfileId();
}
You can try this:
#Query("SELECT u.uiProfile.id from User u where UPPER(u.name)=UPPER('admin')")
Integer getUIProfileId();
Here User is the domain class name and u is the reference of User. with u we will access User's field NOT the column name which are specified with #Column or #JoinColumn Ex : #JoinColumn(name = "UIProfileID", nullable = false).

Infinite Loop When Collection Mapping With Dozer

I'm developing a project which uses BackboneJS in front-end and Java - Spring Core in back-end. I have a problem about mapping entity(domain) objects to DTO objects. I am getting an error message like that :
org.apache.cxf.interceptor.Fault: Infinite recursion (StackOverflowError) (through reference chain: com.countdown.dto.CategoryDTO["countdownList"]->java.util.ArrayList[0]->com.countdown.dto.CountdownDTO["category"]->.......
User.java
#Entity
#Table(name = "Users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "USER_ID", nullable = false)
private int id;
#Column(name = "EMAIL", nullable = false, unique = true)
private String email;
#Column(name = "NAME_SURNAME", nullable = false)
private String nameSurname;
#Column(name = "PASSWORD", nullable = false)
private String password;
#Column(name = "USERNAME", nullable = false, unique = true)
private String username;
#Column(name = "REGISTER_DATE", nullable = false)
private Date registerDate;
#ManyToOne
#JoinColumn(name = "ROLE_ID")
private Role role;
#OneToMany(mappedBy = "createUser")
private List<Countdown> createCountdownList = new ArrayList<Countdown>();
#OneToMany(mappedBy = "updateUser")
private List<Countdown> updateCountdownList = new ArrayList<Countdown>();
#ManyToMany
#JoinTable(name = "FOLLOWINGS",
joinColumns = #JoinColumn(name = "USER_ID"),
inverseJoinColumns = #JoinColumn(name = "COUNTDOWN_ID"))
private List<Countdown> followings = new ArrayList<Countdown>();
//Getters and setters..
}
Role.java
#Entity
public class Role implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ROLE_ID")
private int id;
#Column(name = "ROLE_NAME", nullable = false)
private String roleName;
#OneToMany(mappedBy = "role",fetch = FetchType.LAZY)
List<User> userList = new ArrayList<User>();
}
Countdown.java
#Entity
#Table(name = "COUNTDOWN")
public class Countdown implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "COUNTDOWN_ID")
private int id;
#Column(name = "COUNTDOWN_NAME", nullable = false)
private String countdownName;
#Column(name = "COUNTDOWN_DATE", nullable = false)
private Date countdownDate;
#Column(columnDefinition = "varchar(5000)")
private String countdownDescription;
#JoinColumn(name = "CATEGORY_ID", nullable = false)
#ManyToOne
private Category category;
#JoinColumn(name = "CREATE_USER", nullable = false)
#ManyToOne
private User createUser;
#Column(name = "CREATE_DATE", nullable = false)
private Date createDate;
#JoinColumn(name = "UPDATE_USER", nullable = false)
#ManyToOne
private User updateUser;
#Column(name = "UPDATE_DATE", nullable = false)
private Date updateDate;
#Column(name = "CREATE_USER_IP", nullable = false)
private int createIP;
#ManyToMany
private List<User> followers = new ArrayList<User>();
}
Category.java
#Entity
#Table(name="CATEGORY")
public class Category implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="CATEGORY_ID")
private int id;
#Column(name = "CATEGORY_NAME" , nullable = false)
private String categoryName;
#OneToMany(mappedBy = "category")
private List<Countdown> countdownList = new ArrayList<Countdown>();
}
Business Logic : CategoryServiceImpl.java
I'm getting error in forEach loop.
#Transactional(readOnly = true)
public List<CategoryDTO> getAllCategories() {
List<Category> categoryList;
List<CategoryDTO> categoryDTOList = new ArrayList<CategoryDTO>();
logger.debug("getAllCategories called");
try {
categoryList = categoryDAO.findAll();
for(Category category : categoryList){
categoryDTOList.add(mapper.map(category,CategoryDTO.class));
}
}catch (NoResultException e){
logger.error("getAllCategories method : No Category wasn't found");
logger.warn(e,e);
}catch (Exception e){
logger.error("getAllCategories method : Categories wasn't found");
logger.warn(e,e);
}
return categoryDTOList;
}
Also Do I have to use DTO object in Presentation Layer? Can I use entity objects in presentation layer instead of DTO objects?
How can I solve this problem? Sorry my bad english. Thank you!
Please Try :
#Transactional(readOnly = true)
public List<CategoryDTO> getAllCategories() {
List<Category> categoryList;
List<CategoryDTO> categoryDTOList = new ArrayList<CategoryDTO>();
logger.debug("getAllCategories called");
try {
categoryList = categoryDAO.findAll();
for(Category category : categoryList){
if(category.getCountdownList() != null && !category.getCountdownList().isEmpty()){
for(Countdown countdown : category.getCountdownList()){
countdown.setCategory(null);
}
}
categoryDTOList.add(mapper.map(category,CategoryDTO.class));
}
}catch (NoResultException e){
logger.error("getAllCategories method : Hata: No Category wasn't found");
logger.warn(e,e);
}catch (Exception e){
logger.error("getAllCategories method : Hata: Categories wasn't found");
logger.warn(e,e);
}
return categoryDTOList;
}
For those who are struggling with infinite recursion issue in Dozer.
I use mapId to define a leaf object and stops the recursion.
Let assume we have two entities Course and Teacher, which contains a Many-to-Many relationship, and we want to convert the following object graph to one represented by CourseDTO and TeacherDto. And we hope Dozer stops at the 3rd level.
Teacher 1 ---> m Course 1 ---> m Teacher ---> ...
1st level 2nd level 3rd level
We can first define the following definition for Teacher to TeacherDTO conversion.
This first mapping is used for the root Teacher entity.
Include any other fields you need in the mapping.
mapping(Teacher.class, TeacherDTO.class,
TypeMappingOptions.oneWay()
, mapNull(false)
).fields("courses", "courses");
The following mapping will prevent Dozer from going further to map the contained Course. We define a mapId teacherLeaf for it.
Exclude the fields that cause the infinite recursion. (In my example, it's courses)
Include any other fields you need in the mapping.
mapping(Teacher.class, TeacherDTO.class,
TypeMappingOptions.oneWay(), TypeMappingOptions.mapId("teacherLeaf")
, mapNull(false)
).exclude("courses");
The last one is the mapping rule for Course to courseDTO. The key is that we tell the mapper to use the teacherLeaf mapping rule defined previously to convert the contained Teachers.
mapping(Course.class, CourseDTO.class,
TypeMappingOptions.oneWay()
, mapNull(false)
).fields("teachers", "teachers", useMapId("teacherLeaf"));
Hope this helps!
I use Dozer 6.1.0.

Categories

Resources