How to read specific fields from mapped object in Hibernate - json

I've got two entity objects in my database: UserEntity and ItemEntity and they're mapped with OneToMany relationship.
Here is my code:
UserEntity:
#Entity
#Table(name = "users")
public class UserEntity {
#Id
#Column(name = "user_id")
#GeneratedValue
public int user_id;
#Column(name = "userlogin")
public String userlogin;
#Column(name = "userpass")
public String userpass;
#Column(name = "name")
public String name;
#Column(name = "email")
public String email;
....
#JsonBackReference
#OneToMany(mappedBy="user", cascade = { CascadeType.MERGE },fetch=FetchType.EAGER)
private List<ItemEntity> items;
ItemEntity:
#Entity
#Table(name = "items")
public class ItemEntity {
#Id
#Column(name = "id")
#GeneratedValue
private int id;
#Column(name = "title")
public String title;
#Column(name = "info")
public String info;
#JsonManagedReference
#ManyToOne
#JoinColumn(name="user_id")
private UserEntity user;
And now I'm trying to read all my Items from my database with specific fields from users that owns current item. I need only UserEntity name and email.
This code:
Query query = this.sessionFactory.getCurrentSession().createQuery("from ItemEntity WHERE title = :title");
returns all fields from UserEntity also, because it's mapped, but I don't want that, because I'm sending that data as JSON, and someone can see all informations about user who own that item (like user login and password) in some dev tools like Chrome.
How to reach that?

I'd suggest you use DTO.
Covert your entities to DTO and then transform the DTO objects to
json string.
In the DTO populate only those field that you want as part of your response.
This would make your design more clean.

In addition to what's jitsonfire is suggesting, you can write a query like this
select name, email from ItemEntity WHERE title = :title
than get your results like
List<Object[]> result = query.list();
The object array will contain your columns, the list element will equal to rows, so you can do something like
for (Object[] tuple : result) {
tuple[0]; //name
tuple[1]; // email
}

Related

How to GET data in the JSON format form the DB Repository

I have this JPA Class, where I have 3 columns id, name and date. The Database is already filled with data, where each entry has an id.
#Data
#Entity
#Table(name = "TEST", schema = "TESTSCHEMA")
public class TestDataJpaRecord implements Serializable {
private static final long serialVersionUID = 1L;
TestDataJpaRecord(){
// default constructor
}
public TestDataJpaRecord(
String name,
Date date,
){
this.name = name;
this.date = date;
}
#Id
#Column(name = "ID", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "TEST_SEQUENCE")
#SequenceGenerator(
sequenceName = "TEST_SEQUENCE", allocationSize = 1,
name = "TEST_SEQUENCEx")
private Long id;
#Column(name = "NAME")
private String name;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "DATE")
private Date date;
}
I created a JPA repository for all the data.
public interface TestDataJpaRecordRepository extends JpaRepository<TestDataJpaRecord, Long> {
}
I want to get the data from the DB in a JSON format.
Here is my Rest GET Api. Here I return the data as a string just, but I want to return them as JSON.
#GetMapping(value = "data/{id}")
private ResponseEntity<?> getDataFromTheDB(#PathVariable("id") Long id) {
// get one entry form the DB
TestDataJpaRecord testDataJpaRecord =testDataJpaRecordRepository.findOne(id);
// Here I want to return a JSON instead of a String
return new ResponseEntity<>(testDataJpaRecord.toString(), HttpStatus.OK);
}
Any idea on how I could return the data as JSON and not as a string from the DB?
I would very very much appreciate any suggestion.
If you have Jackson on the classpath which you should if you have used the spring-boot-starter-web then simply:
#GetMapping(value = "data/{id}")
private ResponseEntity<TestDataJpaRecord> getDataFromTheDB(#PathVariable("id") Long id) {
TestDataJpaRecord testDataJpaRecord =testDataJpaRecordRepository.findOne(id);
return new ResponseEntity.ok(testDataJpaRecord);
}
This assumes you have annoted your controller with #RestController rather than #Controller. If not then you can either do that or, annotate your controller method with #ResponseBody.
With Spring Data's web support enabled (which it should be by default with Spring Boot) then you can also simplify as below:
#GetMapping(value = "data/{id}")
private ResponseEntity<TestDataJpaRecord>
getDataFromTheDB(#PathVariable("id") TestDataJpaRecord record) {
return new ResponseEntity.ok(record);
}
See:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web.basic.domain-class-converter

JPA mapping for one-to-many collection of shared data, with user specific values

I have a User model that contains a list of achievements
#Table(name = "user")
#Entity
#NamedEntityGraph(name = "User.achievements",
attributeNodes={
#NamedAttributeNode("achievements")
})
#Data
public class User {
#Id
#NotNull
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#ElementCollection(fetch = FetchType.LAZY, targetClass = Achievement.class)
private List<Achievement> achievements = new ArrayList<>();
}
Here's the achievement model
#Entity
#Data
#Table(name = "achievement")
public class Achievement {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String achievementId;
#Column(name = "title")
private String title;
#Column(name = "description")
private String description;
#Column(name = "achieved", columnDefinition="BOOLEAN DEFAULT false", nullable = false)
private boolean achieved = false;
user_achievements table generated from #ElementCollection mapping, which atm only contains user and achievement foreign keys
I am looking to move the boolean achieved value to the user_achievements table, ideally without having to create a separate model User_Achievements
I am fairly new to using Jpa, but i feel like this scenario is too basic so there must be a straight forward way to do that i cant seem to locate it
#Entity
class UserAchievement {
#EmbeddableId
UserAchievementId id;
#ManyToOne(fetch=LAZY)
#JoinColumn(name="user_username", insertable=false, updatable=false)
User user;
#ManyToOne(fetch=LAZY)
#JoinColumn(name="achivement_achivement_id", insertable=false, updatable=false)
Achivement achivement;
// and other fields
}
class User {
// ...
#OneToMany(mappedBy="user")
List<UserAchievement> userAchievements;
}
and you need to define UserAchievementId

How to apply server side validation on List of Object in spring boot? spring not validating List<Address> which is present in Employee Class

I want to validate List of Address and all its properties city, state , pincode
I have given all spring validation in Address class still its not validating.
Address Class
#Entity
#Table(name = "ADDRESS")
public class Address {
#Id
#GeneratedValue
#Column(name = "ADDR_ID")
private Long addr_id;
#NotNull(message="city should not be null")
#Column(name = "CITY")
private String city;
#NotNull(message="state should not be null")
#Column(name = "STATE")
private String state;
#NotNull(message="pincode should not be null")
#Column(name = "PINCODE")
private Integer pincode;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "FK_EMP_ID", referencedColumnName="ID")
#JsonBackReference
#Valid
#NotNull
private Employee emp;
//getter & setter omitted for breveity
Employee Class
#Entity
#Table(name = "EMPLOYEE")
public class Employee {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#NotNull(message="name should not be null")
#Size(min=1, max=15 ,message="Name should be min 1 and max 15")
#Column(name = "EMPLOYEE_NAME")
private String name;
#NotNull(message="salary should not be null")
#Column(name = "EMPLOYEE_SALARY")
private Integer salary;
#NotNull(message="department should not be null")
#Column(name = "DEPARTMENT")
private String department;
#OneToMany(mappedBy="emp", cascade={CascadeType.ALL},
orphanRemoval=true,fetch=FetchType.LAZY)
#JsonManagedReference
#NotNull(message="address should contain atleast 1 address")
private List<Address> address = new ArrayList<Address>();
//getter & setter omitted for breveity
PostMan Json
{
"name": "aakash",
"salary": 546,
"department": "IT",
"age":32,
"email":"agag#gma",
"address":[
{
"city":"Mumbai"
}
]
}
When i pass list of address and some of its properties it's showing following
error.
javax.validation.ConstraintViolationException: Validation failed for classes
[com.example.springDataJPA.model.Address] during persist time for groups
[javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='pincode should not be null',
propertyPath=pincode, rootBeanClass=class
com.example.springDataJPA.model.Address, messageTemplate='pincode should not
be null'}
ConstraintViolationImpl{interpolatedMessage='state should not be null',
propertyPath=state, rootBeanClass=class
com.example.springDataJPA.model.Address, messageTemplate='state should not
be null'}
I want to validate the list of address with all properties and should and it should return proper default message in json format.
#NotNull checks only if the list is not null. Try to use #NotEmpty instead.
(The good article about the difference: https://www.baeldung.com/java-bean-validation-not-null-empty-blank)

Spring Data JPA #OneToMany infinite loop exception

OneToMany relationship causing infinite loop using Spring Data JPA with hibernate as provider
The problem here is not the type of exception but the infinite loop that causes this exception
I tried #JsonIgnoreProperties which gives me another error => 'Could not write JSON: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer'
The post referencing the solution does not have a solution that adresses my problem.
One says use #JsonManagedReference and #JsonBackReference that does stop the recursion but excludes the object (UserGroup in 'myUser' entity) from the result which I need when I want an object of 'myUser' entity.
The other one says about overriding ToString method which I don't do.
Another one explains why there is an infinite loop and suggest as solution to not do that way. I quote "Try to create DTO or Value Object (simple POJO) without cycles from returned model and then return it."
And this one Difference between #JsonIgnore and #JsonBackReference, #JsonManagedReference explains the difference but doing so I will have the same problem as the first one
'myUser' entity
#Entity
public class MyUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
private Integer age;
//#JsonIgnoreProperties({"myUsers"})
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "userGroupId")
private UserGroup userGroup;
'UserGroup' entity
#Entity
public class UserGroup {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer groupOrder;
#OneToMany
(
mappedBy = "userGroup",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<MyUser> myUsers;
change the getUserGroup() method in your MyUser class as follows.
#Entity
public class MyUser
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
private Integer age;
//#JsonIgnoreProperties({"myUsers"})
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "userGroupId")
private UserGroup userGroup;
public UserGroup getUserGroup()
{
userGroup.setMyUsers(null);
return userGroup;
}
}
you need to add #JsonIgnore annotation at #OneToMany
like this
#JsonIgnore
#OneToMany
(
mappedBy = "userGroup",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<MyUser> myUsers;
I think I'm getting the point of your problem. You want to fetch MyUser including the userGroup data without the circular reference.
Based from the solutions you enumerated, I suggest you should still use the #JsonBackReference and #JsonManagedReference to prevent recursion on your entities and for the solution on your problem, you can try to use a mapper (MapStruck) and map the userGroup details to a DTO during the retrieval of data from the service.
DTOs:
public class MyUserDto {
private Long id;
private String firstName;
private String lastName;
private String email;
private Integer age;
private UserGroupDto userGroupDto;
}
public class UserGroupDto {
private Long id;
private Integer groupOrder;
}
Mapper (MapStruck):
#Mapper(componentModel = "spring")
public interface MyUserMapper {
MyUserMapper INSTANCE = Mappers.getMapper(MyUserMapper.class);
UserGroupDto userGroupToDto(UserGroup userGroup);
#Mapping(source = "myUser.userGroup", target = "userGroupDto")
MyUserDto myUserToDto(MyUser myUser);
}
After retrieving the data from your repository, you may then call the myUserToDto method to map the entity to a DTO.
This is just one way of solving your problem.

In Spring Data how to read entity by foreign key value instead of join?

I am using Spring Data and I am in doubt why even after declaring foreign entities as Lazy loaded they are getting eagerly loaded for this method:
findByReportingManager_IdAndAndLevel(Long reporterManagerId, Integer level)
On logs, I can see the query as:
select userhierar0_.id as id1_28_,
userhierar0_.LEVEL as LEVEL5_28_,
userhierar0_.REPORTING_MANAGER_ID as REPORTIN9_28_,
userhierar0_.USER_ID as USER_ID10_28_
from USER_HIERARCHY userhierar0_
left outer join
USER_V3 user1_ on userhierar0_.REPORTING_MANAGER_ID=user1_.id
where user1_.id=? and userhierar0_.CUSTOMER_ID=? and userhierar0_.LEVEL=?
why extra join even if I am passing reporting manager id ?
UserHierarchy Class:
#Entity
#Table(name = "USER_HIERARCHY")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserHierarchy {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY) // LAZY LOADING
#JoinColumn(name = "USER_ID",referencedColumnName = "ID")
private User user;
#ManyToOne(fetch = FetchType.LAZY) //LAZY LOADING
#JoinColumn(name = "REPORTING_MANAGER_ID", referencedColumnName = "ID")
private User reportingManager;
#Column(name = "LEVEL")
private Integer level;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public User getReportingManager() {
return reportingManager;
}
public void setReportingManager(User reportingManager) {
this.reportingManager = reportingManager;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
#Override
public String toString() {
return ReflectionToStringBuilder.toStringExclude(this, "user", "reportingManager");
}
User Entity class
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue
private Long id;
#Column(name = "EMAIL")
private String email;
#Column(name = "CUSTOMER_ID")
private Long customerId;
#Column(name = "STATUS")
private String status;
// Getter and Setter
As per Spring's doc:
At query creation time you already make sure that the parsed property
is a property of the managed domain class.
So does that mean in order to make User object in "managed state" it uses join or I am wrong in the implementation ?
I stumbled across the same problem recently and it seems that there is no solution for this at the moment in Spring Data.
However I've created a ticket for it.
If you go with Criteria API or JPQL for this particular query, then it will work properly.