Hi i am new to hibernate.
I have a java web project and i am using spring mvc with hibernate in this project and for database i am using my sql.
The issue i am facing is that :
i have a table in db as user and in java project as user.java
and i have another table references and in java project i have referances.java
and i have created a mapping table as user_referances_mapping(user_id, referance_id).
and i have one to many relationship between user and referances table.
Now when i try to get the user data it gives me user table coloumns data but not the referances data and returns only null list for referances table.
On the other hand i also have similar mapping tables with user table like role, address with one to many mapping only and data is getting retrieved from those tables.
So can anyone help me getting the solution, that why i am not getting the data of the referances table using the one to many relationship and what should be the solution to it.
mapping of referances table in user table:
#OneToMany
#Basic(optional = true)
#BatchSize(size = 5)
#Cascade(CascadeType.ALL)
#Cache(region = IAppConstants.CACHE_REFERANCES, usage = CacheConcurrencyStrategy.READ_WRITE)
#JoinTable(name = "user_referances_mapping", joinColumns = { #JoinColumn(name = "user_id") }, inverseJoinColumns = { #JoinColumn(name = "referance_id") })
private List<Referances> referances = new ArrayList<Referances>();
public List<Referances> getReferances() {
return referances;
}
public void setReferances(List<Referances> referances) {
this.referances = referances;
}
UserDao class function :
public User getC2SUserByContactNoOrEmail(final String value) throws ApplicationException {
try{
#SuppressWarnings("unchecked")
Query query = currentSession().createQuery(
IQueryConstants.FETCH_USER_BY_CONTACTNO_OR_EMAIL);
query.setParameter("contactNo", value);
query.setParameter("email", value);
return (User) query.uniqueResult();
}catch(Exception e){
throw new ApplicationException(
"Issue occurred while fetching user by: " + value, e);
}
//return null;
}
FETCH_USER_BY_CONTACTNO_OR_EMAIL = "FROM User WHERE contactNo=:contactNo or email=:email";
If I'm right, the OneToMany relations are defined as "lazy" by default, which means you need to explicitly state that you want to fetch the related records.
Try modifying the query like this:
FETCH_USER_BY_CONTACTNO_OR_EMAIL = "FROM User u LEFT JOIN FETCH u.referances WHERE u.contactNo=:contactNo or u.email=:email";
Related
I am working with 2 entities. Let's call the entities Company (database table companies) and Employee (database table employees). The 2 entities are associated in a many to many relationship. Thier associations are held in a database table - lets call it company_to_employee.
The Employee object contains no references to Company while the Company object contains the following for it's association to Employee.
// Inside Company.java
#ManyToMany
#JoinTable(name = "company_to_employee", joinColumns = { #JoinColumn(name = "company_id") }, inverseJoinColumns = { #JoinColumn(name = "employee_id") })
private Set<Employee> employees;
I have a database query that is run when a user logs in which grabs the active companies that they own.
public List<Company> getActiveCompanies(String userId) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Company> query = builder.createQuery(Company.class);
Root<Company> company = query.from(Company.class);
Join<Company, CompanyUser> owner = company.join(Company_.owner);
Join<CompanyUser,User> user = owner.join(CompanyUser_.user);
Predicate predicate = builder.and(
builder.equal(user.get(User_.id), userId),
builder.equal(company.get(Company_.enabled), true),
builder.equal(owner.get(CompanyUser_.enabled), true));
query.where(predicate);
query.orderBy(builder.asc(builder.lower(company.get(Company_.name))));
return entityManager.createQuery(query).getResultList();
}
The returned list of companies is used by the front end to display companies along with their employees, etc.
I recently added a new enabled field onto Employee. However, the employees on the companies returned by the above query do not pick up changes to this field.
#Column(name = "ENABLED")
private boolean enabled;
I've tried replacing the query's last line with the following with no luck (caching hint).
return entityManager.createQuery(query).setHint("org.hibernate.cacheable", false).getResultList();
I've also tried updating the mapping to #ManyToMany(fetch = FetchType.EAGER) but this also did nothing. And the two attempts did nothing in combination with one another.
The only way I've been able to fix the issue is to add the following to the beginning of the query, but I'd rather not have to flush the whole cache like this.
entityManager.unwrap(Session.class).clear();
Am I missing something?
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;
}
I have 2 tables: User and Loan. User have 3 fields: id (PK), first_name and last_name. Loan table have field user_id that is foreign key to User table:
The logic of my application: when I create new Loan object and set there a corresponding user it should create a new one for unique user or set a id of a user into user_id for existing user.
For that purpose my database have a unique index on first_name, last_name.
The Loan class uses User with #ManyToOne relationship:
public class Loan {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "user_id")
private User user;
... methods ...
When I add new user, everything is fine, it persists to db with new PK. But when I try to add an existing one I got and exception:
javax.servlet.ServletException: org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'FIRST_LAST_NAME' defined on 'USER'.
Error Code: 20000
Again when I put #ManyToOne(cascade = CascadeType.MERGE) I'm able only to enter existing users, as only I pass a new one that is not persisted in DB I got an exception:
javax.servlet.ServletException: org.springframework.dao.InvalidDataAccessApiUsageException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: User{id=null, firstName='a', lastName='a'}.;
#ManyToOne(cascade = CascadeType.ALL) didn't work as well.
UPDATE
the code for insertion new Loan is:
user = userService.findByName(firstName, lastName);
if (user == null) {
user = new User(firstName, lastName);
}
loan.setUser(user);
loanService.save(loan);
findByName() and save() methods are:
private EntityManager em;
public User findByName(String firstName, String lastName) {
TypedQuery<User> query = em.createQuery(
"SELECT u FROM User u WHERE u.firstName = :firstName " +
"AND u.lastName = :lastName", User.class)
.setParameter("firstName", firstName).setParameter("lastName", lastName);
List<User> users = query.getResultList();
if (!users.isEmpty()) {
return users.iterator().next();
} else {
return null;
}
}
public void save(User user) {
if (user.getId() == null) {
em.persist(user);
} else {
em.merge(user);
}
}
If you call Persist on Loan
a) with cascade.Persist set on the user relationship and the referenced user exists, set you will get an exception if the User instance was not read in from the same EntityManager instance/context - You are reading it through the userService and then saving the loan in the loanService, so the context would have to be container managed and within the same transaction to work.
b) If cascade.PERSIST (or cascade.ALL) is not set and the reference user is new, you will get the "new object was found" exception.
If you cannot perform the read of the user in the same EntityManager you are going to save the loan in, switching to using merge may help, as JPA should then check referenced entities and merge as appropriate. You will then need to have the cascade.MERGE option set on the relationships for the merge to be applied to the User instance.
Note that using merge is different from Persist in that what you passed in does not become managed by the context. That means after the transaction commits, the entities passed in will still not have any primary key values set. You may want to pass back the entity returned from Merge if you are going to continue to use the entities for any other operations.
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.
I am trying to create a simple example to understand how collection of basic and embaddable types works in Hibernate.
I have created a User entity with a set of nickNames and also a set of addresses. Here are my Java classes:
User.java
#Entity
#Table(name = "TB_User")
public class User {
#Id
#GeneratedValue
private int id;
private String name;
#ElementCollection
#CollectionTable(name = "Nicknames", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "nickname")
private Set<String> nickNames = new HashSet<String>();
#ElementCollection
#CollectionTable(name = "Addresses", joinColumns = #JoinColumn(name = "user_id"))
#AttributeOverrides({ #AttributeOverride(name = "street1", column = #Column(name = "fld_street")) })
public Set<Address> addresses = new HashSet<Address>();
public User() {
}
public User(String name, Address... addresses) {
this.name = name;
this.addresses.addAll(Arrays.asList(addresses));
}
public void addNickName(String... nickNames) {
this.nickNames.addAll(Arrays.asList(nickNames));
}
// Setters & Getters
}
Address.java
#Embeddable
public class Address {
private String street1;
public Address() {}
public Address(String street1) {
this.street1 = street1;
}
#Override
public String toString() {
return street1;
}
// Setters & Getters
}
Now I have created a simple program to create users in database and then show the list of users. Here is my code:
private static void saveUsers() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.getTransaction().begin();
User user = new User("User", new Address("abc"),
new Address("xyz"));
user1.addNickName("alpha", "beta");
session.save(user);
session.getTransaction().commit();
}
private static void showUsers() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.getTransaction().begin();
List<User> users = session.createQuery("from User").list();
for (User user : users) {
System.out.println(user.getName() + " -- > " + user.getNickNames()
+ " --> " + user.getAddresses());
}
session.getTransaction().commit();
}
When I run this program, I observed that hibernate issues below set of commands:
Hibernate: select user0_.id as id1_2_, user0_.name as name2_2_ from TB_User user0_
Hibernate: select nicknames0_.user_id as user_id1_2_0_, nicknames0_.nickname as nickname2_1_0_ from Nicknames nicknames0_ where nicknames0_.user_id=?
Hibernate: select addresses0_.user_id as user_id1_2_0_, addresses0_.fld_street as fld_street2_0_0_ from Addresses addresses0_ where addresses0_.user_id=?
User -- > [alpha, beta] --> [xyz, abc]
Hibernate: delete from Addresses where user_id=?
Hibernate: insert into Addresses (user_id, fld_street) values (?, ?)
Hibernate: delete from Addresses where user_id=?
Hibernate: insert into Addresses (user_id, fld_street) values (?, ?)
If I try to get the list of addresses using user.getAddresses() with in session, then Hibernate deletes & re-inserts records in Addresses table.
Why hibernate tries to delete and re-create records in Addresses table, as this causes performance issue. Also why it is not applicable to basic types like nickNames in my example and not running update commands for the property nickNames?
This behaviour is related to the fact, that there is not bi-directional mapping. To understand that in depth - please read this article:
inverse = “true” example and explanation
And here is the way how to do that with annotation:
inverse=true in JPA annotations
Let me cite from the answer:
I found an answer to this. The mappedBy attribute of #OneToMany annotation behaves the same as inverse = true in the xml file.
Some summary:
Hibernate can issue SQL statements which we would expect. But only, if we do have bidirectional mapping in place.
Then, the other end (address) will be driving the persistence. It will/must know about its parent - and that's why some direct UPDATE statements could be issued.
So, if we want to avoid DELETE and INSERT, we have to use the inverse mapping. Hibernate will issue more "expectable" SQL.
This one is quite old, but might be relevant to sombody. Another tip that helped in my case: if you can separate reads from writes, consider using
#Transactional(readOnly = true)
That makes underlying JPA framework know that no modifications are expected in the current transaction. In practice, Hibernate no longer would run delete + insert when you merely want fetching some data.