Hibernate add an entity with foreign key using REST - java

First of all, I haven't written any SQL statements to create a table. I try to use Hibernate/jpa only without writing SQL.
My relation is in the following: A user can have many task, a task only has one user.
I created my models as this:
User Table:
#Entity
#Table(name="T_USER")
#EntityListeners(AuditingEntityListener.class)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "uid")
private Long uid;
...
}
Task Table:
#Entity
#Table(name = "T_TASK")
#EntityListeners(AuditingEntityListener.class)
public class TASK{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long tid;
#ManyToOne
#JoinColumn(name ="oid")
private User owner;
public User getOwner() {
return owner;
}
public void setOwner(User owner) {
this.owner = owner;
}
...
}
The relation is task's ownerid(oid) is user's uid.
To save a user to my database, I'm using postman with the following parameters:
{
"username": "firstuser",
"email": "firstuser#email.com"
}
To save a task to my database I'm using this:
{
"description": "some description",
"oid": "12" // I also can state username of the user rather than ID
}
However, as I execute save a task, the oid of task is NULL. In data access objects, I have:
#PostMapping("/save")
public QR createTask(#Valid #RequestBody Task task)
{
return taskDAO.save(task);
}
1-What am I doing wrong? I just want to add a task with owner id to database, however it returns null as ownerid.
2-Should I create a table first with SQL using
Create table task(
tid BIGINT,
description VARCHAR(255),
oid BIGINT,
PRIMARY KEY(tid), FOREIGN KEY(oid) REFERENCES (user.uid))
3-Should I change my save method in TaskDAO?
public Task save(Task task)
{
return taskRepository.save(task);
}
4- Should I change my controller method(createTask method using RESTcall)
5- Assume that all of the problems above is fixed. How can I fetch all task that a user has?
6- How can I delete a task when a user is deleted(cascase in SQL, but is there any method in Hibernate)
I hope I explained my problem. Any feedback will be appreciated.

1-What am I doing wrong? I just want to add a task with owner id to database, however it returns null as ownerid
First of all, I would make sure that the owner is being persisted in the db, just to be sure that you have a value to be referencing
2-Should I create a table first with SQL using
Since you're using ORM, writing an SQL query would defeat the purpose of that, you could, but it's not all that necessary, since the relationships are specified already
3-Should I change my save method in TaskDAO?
4- Should I change my controller method(createTask method using RESTcall)
I think it would be best to change your createTask method, you could include the user's id as a pathvariable or a queryparameter and in that method you find the user using their id and set the user field in the task before passing it to the dto to save the value.
The reason the oid is null is because you do not have such a field in there.
5- Assume that all of the problems above is fixed. How can I fetch all task that a user has?
In your task repository, you can create a method like
Collection<Task> findAllTasksByOwnerId(Long id);
6- How can I delete a task when a user is deleted(cascase in SQL, but is there any method in Hibernate)
You can specify the cascade type where you have specified the relationship between the task and the user
You can check this link for a simple tutorial on how to cascade in spring

The oid is null because there is no such field in Task. I think you are mixing two concepts here. The first one is the data transfer object that represents your REST data structure. This one should have an oid field. The second one is the persisting entity. This one you have, it's the Task class.
I would implement a TaskDTO, use Hibernate's session to load the User by its id, then build a Task from the User and the other fields from TaskDTO, then save the Task like you do.
Regarding your other questions
2 - With a create or updatevalue for hibernate.hbm2ddl.auto, Hibernate can generate or update the tables when you start the application.
5 - You could have this interface
#GetMapping("/user/{id}")
public List<TaskDTO> getTasks(#PathVariable Long id)
Then I think you can't escape coding a criteria query of some sort.
6 - This is done with configuring the relation with cascade = CascadeType.ALL

The hibernate builds table and foreign keys automatically.Complex queries we can write in repo/controller in hibernate syntax.
With Crud repository we can delete , create update and read data easily.
for example we have student to course one to many relation.
#OneToMany
#JoinColumn(name = "studentId", referencedColumnName = "studentId", insertable = false, updatable = false)
private List<StudentCourses> studentCourses;
in StudentController I will write
#CrossOrigin(origins = "http://localhost:8090")
#RequestMapping(value = "/registerStudentCourse", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces =
MediaType.APPLICATION_JSON_VALUE )
public StudentCourses registerStudentCourse(#RequestBody StudentCourses studentCourse) {
if (studentCourse != null) {
studentCourse.setLastupdated(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date()));
studentCourse = studentCourseRepository.save(studentCourse);
}
return studentCourse;
}
#CrossOrigin(origins = "http://localhost:8090")
#RequestMapping(value = "/findStudentCourses", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces =
MediaType.APPLICATION_JSON_VALUE )
public List<StudentCourses> findStudentCourses(#RequestBody String studentId) {
List<StudentCourses> courses = new ArrayList<>();
JSONObject requestedJSONObject;
try {
requestedJSONObject = new JSONObject(studentId);
String student = requestedJSONObject.getString("studentId");
courses =
studentCourseRepository.findCoursesByStudent(Long.parseLong(student));
} catch (JSONException e) {
// TODO Auto-generated catch block
}
return courses;
}

Related

JPA Repository.findById() returns null but the value is exist on db

I'm developing Spring boot project, using JPA.
What I wanna know is repository.findById(id) method returns null, whereas data is available in the DB.
Functions save() and findAll() are working fine. When I ran the same code on junit test environment, but it completely worked. If the data is hard coded, like memberRepository.findById("M001");, it working fine.
Entity
#Entity
#Table(name="df_member")
public class DfMember {
#Column(name="member_type")
private String memberType;
#Id
#Column(name="id")
private String id;
...columns...
...Getters/Setters.....
Controller
#ResponseBody
#RequestMapping(value="/checkIdDuplicate", method=RequestMethod.POST)
public boolean checkIdDuplicate(#RequestBody String id) {
return memberService.isExistByUserId(id);
}
MemberService
public boolean isExistByUserId(String id) {
Optional<DfMember> member = memberRepository.findById(id);
return member.isPresent();
}
Repository
public interface MemberRepository extends CrudRepository<DfMember, String> {
}
Should return Member Object but it's null.
While the OP has solved his issue, I have to add my answer to the same problem, but with a different cause, because I'm sure will be helpful after hours of debugging I found in the source code of Hibernate (5.4.18) a try/catch that when a EntityNotFoundException is thrown in the hydration process the findBy returns null, even if the entity exists, and it's hydrated successfully. This is because a related referenced entity doesn't exists and Hibernate expect it to exists
For example I have two entities Unit and Improvement where I store an unit with id 5 to have an improvement with id 0 (which doesn't exists), then unitRepository.findById() returns null, instead of the Unit entity with id 5.
#Entity
#Table(name = "units")
public class Unit {
#ManyToOne(fetch = FetchType.LAZY)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "improvement_id")
#Cascade({ CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DELETE })
private Improvement improvement;
}
The reason this happened was because an import script used 0 as value for the improvement_id instead of the original NULL.
Hint: Becareful with disabling Foreign Key checks in import scritps
Best regards
I would like to add something with #Alexpandiyan answer. Instead of first finding record by Id and checking if it is present or not. You can directly check in database if id exists or not by using predefined function existsById like below.
public interface MemberRepository extends CrudRepository<DfMember, String> {
boolean existsById(String id);
}
Updated member service function memberService.isExistByUser().
public boolean isExistByUserId(String id) {
return memberRepository.existsById(id);
}
See documentation https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#new-features.1-11-0
You have to change #RequestBody to #RequestParam. Please update your controller code as below.
#ResponseBody
#RequestMapping(value="/checkIdDuplicate", method=RequestMethod.POST)
public boolean checkIdDuplicate(#RequestParam String id) {
return memberService.isExistByUserId(id);
}
This occurred to me because of a script which was inserting data (while disabling foreign key checks), and one of the referenced ID was not existing in the parent table, i.e. :
SET FOREIGN_KEY_CHECKS=0;
INSERT INTO company(id, name) VALUES (1, 'Stackoverflow');
INSERT INTO employee(id, first_name, last_name, company_id) VALUES (1, 'John', 'Doe', 1);
INSERT INTO employee(id, first_name, last_name, company_id) VALUES (2, 'Jane', 'Doe', 2);
SET FOREIGN_KEY_CHECKS=1;
The Jane Doe record references a target primary key value (company_id=2) that does not exist in the company table.
In that case JPA's findById does not return any record even if it exists.
Fixing data intergrity solves the problem.
public boolean isExistByUserId(String id)
{
Optional<DfMember>member = Optional.ofNullable(memberRepository.findById(id).orElse(new DfMember()));
return member.isPresent();
}
Please update your MemberService code.

Could not write JSON: Unable to access lob stream

I'm writing a server with Spring-Boot using PostgreSQL
I'm trying to get information about images that are linked to a specific entity.
I'm trying to get User information from the server to my front-end Angular app.
In my system user have images linked to his account so i did class ImageEntity
#Entity #Table(name = "image") #Data
public class ImageEntity {
#Id #GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
private String type;
#Lob
private byte[] image;
#JsonIgnore
public byte[] getImage() {
return image;
}
}
Then i linked the list of images to user account class
#Entity #Data
public class UserAccount{
#Id #GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String firstName;
private String lastName
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "user_images",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "image_id", referencedColumnName = "id")}
)
private List<ImageEntity> images;
public void addImage(ImageEntity image) {
images.add(image);
}
}
Then i create endpoint to get user by id
#GetMapping("users/{id}")
public Optional<User> getUserById(#PathVariable Long id) {
return service.getUserById(id);
}
service method is very simple
#Transactional
public Optional<User> getUserById(Long id) {
return repository.findById(id);
}
I added some images through another endpoint works fine because i'm able to get image in my front-end.
Problem is when i want to get User info as a JSON from server( and i write #JsonIgnore on #Lob field because i only want to have info of image not the actual image) i get this error
Resolved exception caused by handler execution: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Unable to access lob stream; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unable to access lob stream (through reference chain: com.app.model.user.User["images"])
I read some similar articles and i try to give #JsonIgnore on getter of the Image #Lob image i added #Transactional to service method retrieving elements but it's not working.
I simply want to achieve that kind of message from server:
{
id: "1"
firstName: "test",
lstName: "test_ln",
images: {
{
"id": 10,
"name": "IMG12.jpg",
"type": "image/jpeg"
},
{
"id": 20,
"name": "IMG456.jpg",
"type": "image/jpeg"
}
}
}
The fastest solution (not the best one) is to add fecth = EAGER to the OneToMany images relationship... the problem with this solution is that always will load the images entity (including the byte[] image) when dealing with users entities (which could be a performance problem)...
The next "best" solution is to omit the EAGER configuration described previously, and create a new method in your repository... such method should execute a JPA Query like this:
SELECT ua
FROM
UserAccount ua
LEFT JOIN FECTH ua.images img
WHERE
ua.id = :id
This will load the user and its related images... then in your service, you call such method (the problem with this solution is that loads the byte[] image even if you only want the other attributes of the ImageEntity)
The best solution is to extend the solution #2 to retrieve only the attributes that you want of the ImageEntity, resulting in a query like this:
SELECT
ua,
img.id, img.name, img.type
FROM
UserAccount ua
LEFT JOIN ua.images img
WHERE
ua.id = :id
Then, your repository method should return a JPA Tuple and in your service method you transform that tuple into the User that you want return (including the associated images' metadata) ... (UPDATE) Example (using the method that you indicate in your comments):
// #Transactional // Remove the transactional annotation to avoid cascade issues!
public User getUserById(Long id) {
List<ImageEntity> images;
List<Tuple> tuples;
User user;
tuples = repository.getUserById(id);
user = null;
if (!tuples.isEmpty()) {
user = tuples.get(0).get(0, User.class);
images = new ArrayList<>();
for (Tuple t : tuples) {
if (t.get(1) != null) {
images.add(new ImageEntity(
t.get(1, Long.class),
t.get(2, String.class)
));
}
}
user.setImages(images);
}
return user;
}
In order to this to work, you need:
Modify the signature of the method getUserById (in your repository) to return a List of Tuple
Create a constructor method in the ImageEntity class with this signature: ImageEntity(long id, String name) { ... }
The User Entity should have a method setImages(List<ImageEntity> images) { ... }
UPDATE2: in order to do something like this, while retrieving all users, you will need:
1) Create (or override) a method in the User repository whose query will be like (let's call it, findAll):
SELECT
ua,
img.id, img.name, img.type
FROM
UserAccount ua
LEFT JOIN ua.images img
2) In your service, implement a method like this:
public List<User> findAll(Long id) {
List<ImageEntity> images;
List<Tuple> tuples;
Map<Long, User> index;
tuples = repository.findAll();
index = new HashMap<>();
for (Tuple t : tuples) {
user = t.get(0, User.class);
if (!index.containsKey(user.getId()) {
images = new ArrayList<>();
user.setImages(images);
index.put(user.getId(), user)
} else {
user = index.get(user.getId());
images = user.getImages():
}
if (t.get(1) != null) {
images.add(new ImageEntity(
t.get(1, Long.class),
t.get(2, String.class)
));
}
}
return index.values();
}
EXPLANATION: The key point is we want to retrieve the user with the image metadata (only code, name and type) avoiding to load the lob attribute (because, the images can be MB and they won't be used/serialized) ... that is why we execute a query like this:
SELECT
ua,
img.id, img.name, img.type
FROM
UserAccount ua
LEFT JOIN ua.images img
The LEFT JOIN force to retrieve all user (including those without images)
The ORM (i.e. JPA implementation, for example, hibernate) maps this kind of query to a Tuple Object (always)!
The query produces N x M tuples ... where N are the total of Users and M are total of images... for example, if you have only 1 user with 2 images... the result will be 2 tuples, where first tuple's component is the always the same user, the other components will be the attributes of each images...
Then, you need transform the tuple object to a User Object (this is what we do in the service methods) ... the key point here is the use of a new ArrayList for the images attribute, before adding a new ImageEntity to it ... we need to do this because the ORM injects a proxy list for each User loaded ... if we add something to this proxy, the ORM executes the lazy loading of such proxy, retrieving the associated images (which is something that we want to avoid)...

Caused by: org.hibernate.TransientObjectException: The given object has a null identifier: com.models.User on hibernate update

I am trying to make an update in the database row. I am having this exception
Caused by: org.hibernate.TransientObjectException: The given object has a null identifier: com.models.User
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.getUpdateId(DefaultSaveOrUpdateEventListener.java:270)
this is my controller code for the submit action from the jsp file
// create new user object
User user = new User();
user.setName(name);
user.setEmail(email);
user.setActive(false);
_userDao.update(user);
this is my dao that defines the update with hibernate session factory utility
public void update(User user) {
getSession().update(user);
}
//EDITTED: this is my mapping for user entity class
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "userId")
private Integer id;
#Column(nullable = false)
private String name;
#Column(unique = false, nullable = false)
private String email;
#Column(nullable = true)
private boolean active;
I am not able to update the user record where the email address is equal to the one entered in the jsp input form. Kindly assist, this is my first attempt in updating a field with hibernate sessionfactory.
The problem is that you are using update method on not existing entity. If you want to save newly created entity you have to use save or saveOrUpdate.
update method works only if entity already exists in DB.
I had the slimier situation at my work. As #ByeBye said the problem was trying to update an entity which was not persisted before. See following example
SalesOrder salesOrder = consignment.getSalesOrder();
if (salesOrder != null) {
// some code
}
else{
//
salesOrder = new SalesOrder();
consignment.setSalesOrder(salesOrder);
}
salesOrderRepository.update(salesOrder); // hibernate update
return salesOrder;
}
Here when the execution of code comes to else part. It try to create a new sales order object (where the object id is null ) and try to update it. And this cause the mentioned error.
So my fix was simply changing the update to saveOrUpdate . see below
.......
salesOrder = new SalesOrder();
consignment.setSalesOrder(salesOrder);
}
salesOrderRepository.saveOrUpdate(salesOrder);
return salesOrder;
}
So then it commands hibernate to first persist the non-existing objects and then do all the updates upon that (if required ).
Hope this scenario will help to all

Can an entity be updated instead of being inserted using spring-data-jpa when entity ID is not known without issuing a query?

We have a Spring Boot/Data-JPA (1.3.3.RELEASE) application using Hibernate implementation where a CSV file is read and inserted into a database table called FIRE_CSV_UPLOAD. For records that are already present we just update them.
We retrieve record ID by querying for unique key (a combination of three columns) but this approach is inefficient for thousands of record in CSV file.
My question is how to update record without querying the table for unique key? If I do not query for ID then the record will be inserted instead of update.
I know one way which is to retrieve all records and store their unique key and ID pairs in a Map. Any other suggestions are very much appreciated. The codes are as below,
Note: They are minimized for brevity.
#Entity
#Table(name = "FIRE_CSV_UPLOAD",
uniqueConstraints={#UniqueConstraint(columnNames = {"account_number" , "account_type", "bank_client_id"})})
public class FireCsv {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NotNull
#Column(name="account_number")
private String accountNumber;
#NotNull
#Column(name="account_type")
private String accountType;
#NotNull
#Column(name="bank_client_id")
private String bankClientIdNumber;
...
other fields/getters/setters
}
--
public interface FireCsvRepository extends JpaRepository<FireCsv, Long> {
#Query("select u from FireCsv u where u.accountNumber = :accountNumber and u.accountType = :accountType and u.bankClientIdNumber = :bankClientIdNumber ")
FireCsv findRecord(#Param("accountNumber") String accountNumber,
#Param("accountType") String accountType,
#Param("bankClientIdNumber") String bankClientIdNumber);
}
--
#Service
public class FireCsvServiceImpl implements FireCsvService {
other fields/methods
...
#Override
#Transactional
public FireCsv save(final FireCsv fireCsv) {
FireCsv existingFireCsv = fireCsvRepository.findRecord(fireCsv.getAccountNumber(), fireCsv.getAccountType(), fireCsv.getBankClientIdNumber());
// If record exist then mark as update, if not as insert
if (existingFireCsv != null) {
fireCsv.setId(existingFireCsv.getId());
fireCsv.setRecordStatus(CSVUploadRecordStatus.CSV_UPDATE.getStatus());
}
else {
fireCsv.setRecordStatus(CSVUploadRecordStatus.CSV_INSERT.getStatus());
}
fireCsv.setRecordStatusDate(new java.sql.Timestamp(new Date().getTime()));
return fireCsvRepository.save(fireCsv);
}
}
You have to read before deciding to make an update or insert, I dont think there is a way around it.
To make that faster you should add an index to your database
using the three columns "account_number", "account_type", "bank_client_id".
Alternatively you can try to use an composite id using #IdClass as shown in
tutorial-jpa-composite-primary-key
The JPA provider should than automatically create the index for it.

JPA nativeQuery returns cached resultList

I have following classes:
Company.class:
public class Company {
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "company_id") , inverseJoinColumns = #JoinColumn(name = "employee_id") )
#ManyToMany(fetch = FetchType.LAZY)
private Set<Employee> employees;
#Column(name = "score")
private BigDecimal score;
}
and Employee.class
public class Employee {
#ManyToMany(fetch = FetchType.EAGER, mappedBy="employees")
private Set<Company> companies;
}
The Score column of Company is always null in the db and never updated via dao, because there is other table containing score for each unique pair Company-Employee.
I need the value of Score, only for the case when I fetch Employee by id, so this case all Company instances in the Set should contain score, thus I will get Employee-Company score pairs where employee is fetched Employee.
I have following code to achieve that:
public Employee get(Long id) {
Employee emp = (Employee) dao.find(id);
List<Company> compList = compnanyService.getByEmpId(id);
Set<Company> compSet = new HashSet<Company>(compList);
emp.setCompanies(compSet);
return emp;
}
And Company Dao contains method:
public List<Company> getByEmpId(Long id) {
final Query query = this.entityManager.createNativeQuery("select company.comp_id, ...some other fields, score.score from company join score on company.company_id=score.company_id where score.employee_id=:employee_id",
Company.class);
query.setParameter("employee_id", id);
List<Company> comps = query.getResultList();
return comps;
}
The problem is that getByEmpId(id) gives a ResultList where company.score is null though executed in the db it is not null.
I suspected that there is some caching intervening, so I tried to remove some columns from the native query, and it should have invoked an exception with "no column found" (or alike) message while mapping, but this method still gives List<Company> with all fields on their places though Hibernate prints out my native query in the console with all changes I make.
What am I doing wrong here and how to achieve what I need? Thank you.
It might be associated with first level cache, which can be out of sync when using native SQL queries. From here:
If you bypass JPA and execute DML directly on the database, either
through native SQL queries, JDBC, or JPQL UPDATE or DELETE queries,
then the database can be out of synch with the 1st level cache. If you
had accessed objects before executing the DML, they will have the old
state and not include the changes. Depending on what you are doing
this may be ok, otherwise you may want to refresh the affected objects
from the database.
So you can try using refresh method from EntityManager.
So I ended up doing that:
Created view in db from the query:
CREATE VIEW companyscore AS select company.comp_id, score.emp_id ...some other fields, score.score from company join score on company.comp_id=score.comp_id;
Created corresponding entity CompanyScore with composite primary id as comp_id and emp_id and created view as table.
Changed Employee entity to:
public class Employee {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "emp_id")
private Set<CompanyScore> companies;
}
This way I not only have score field always consistent, but I can choose set of fields to show as the whole Company class is quite extensive and I don't need all the fields for this particular case.

Categories