Hibernate HQL Join Fetch returns duplicate rows - java

ProcessSolution Entity :
#Entity
#Table(name="process_solution")
public class ProcessSolution implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="process_id", columnDefinition="INT(10) UNSIGNED")
private Integer processId;
#Column(name="process_name", length=120, nullable=false)
private String processName;
#ElementCollection(fetch=FetchType.LAZY)
//#LazyCollection(LazyCollectionOption.FALSE)
//#Fetch(FetchMode.Select)
#JsonIgnore
#CollectionTable(name="process_solution_step",
joinColumns=#JoinColumn(name="process_id"),
foreignKey=#ForeignKey(name="fk_process_solution_step_process_id")
)
#Column(name="solution_step", length=200, nullable=false)
private List<String> processSolutionSteps = new ArrayList<>();
#ManyToOne
#JoinColumn( name="category_id", columnDefinition="INT(10) UNSIGNED",nullable=false,
foreignKey=#ForeignKey(name="fk_process_solution_category")
)
private Category category;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name="process_solution_employee",
joinColumns={#JoinColumn(name="process_id")},
inverseJoinColumns={#JoinColumn(name="emp_id",columnDefinition="INT(10) UNSIGNED")},
foreignKey=#ForeignKey(name="fk_process_employee_process_solution_process_id"),
inverseForeignKey=#ForeignKey(name="fk_process_employee_employee_emp_id")
)
private Set<Employee> employees = new HashSet<>();
// Getters/Setters
}
And I'm Executing HQL Query in DAO as:
#Override
public ProcessSolution getProcessSolution(Integer processId) {
Session session = this.sessionFactory.openSession();
final String GET_PS = "SELECT ps FROM ProcessSolution ps JOIN FETCH ps.processSolutionSteps JOIN FETCH ps.employees WHERE ps.processId = :processId";
//ProcessSolution processSolution = session.get(ProcessSolution.class, processId);
ProcessSolution processSolution = ( ProcessSolution ) session.createQuery(GET_PS)
.setInteger("processId", processId).uniqueResult();
session.close();
return processSolution;
}
My Problem is I'm Getting ElementCollection i.e. processSolutionSteps repeated (Multiple Rows).
So I changed it From List<> to Set<>, now I'm getting correct result but its order is not preserved.
What I have tried:
For Set I have tried LinkedHashSet but problem still persist.
#LazyCollection(LazyCollectionOption.FALSE) from here
#Fetch(FetchMode.Select) from another SO source
Any Idea how to solve this problem.
Updated :
Sample Data :
**process_solution**
+---------------+----------------+
| process_id | process_name |
+---------------+----------------+
| 3 | process 1 |
+---------------+----------------+
**process_solution_step**
+---------------+----------------+
| process_id | solution_step |
+---------------+----------------+
| 3 | step 1 |
+---------------+----------------+
| 3 | step 2 |
+---------------+----------------+
If I print Process Solution Steps I get the result as
step 1
step 1
step 2
step 2
If I print Employee Lenth I got correct result.

Related

How to query hibernate ManyToMany column using sql

I am using java hibernate to store my data entities. I want to know the sql command to select the data in a #ManyToMany column using postgresql-psql command.
for normal columns, I can just run:
SELECT id FROM university;
But now I have the following university entity:
#Entity
#Table(name = "university")
public class University {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
#ManyToMany(fetch=FetchType.EAGER)
#JoinColumn(name="students" /* referencedColumnName="id" */)
private List<Student> students;
}
#Entity
#Table(name = "student", uniqueConstraints = { #UniqueConstraint(columnNames={"name"})})
public class Student
{
#Id
private Long id;
#NotBlank
#NotNull
private String name;
}
The problem is, I don't know what the student list is called in psql.
When I run:
SELECT students FROM university;
I get:
ERROR: column "students" does not exist
When I type:
\d university
I get (not actual data: data anonymized to student/university example):
Column | Type | Collation | Nullable | Default
------------------+-----------------------------+-----------+----------+---------
id | bigint | | not null |
Indexes:
"university_pkey" PRIMARY KEY, btree (id)
"uk_rwpd2frv6wtkgqtxn3envk3i8" UNIQUE CONSTRAINT, btree (name)
Referenced by:
TABLE "university_students" CONSTRAINT "fkdkjk4jgutu64g937gkknybax2" FOREIGN KEY (university) REFERENCES university(id)
You have a table 'university_students', can you do 'select * from university_students'
I think you have this structure :
student:
id
name
0
first_student
university:
id
3
university_students:
university_id
students_id
3
0
So the only think you need to do is this :
SELECT * FROM student WHERE id IN (SELECT students_id FROM university_students WHERE university_id = 3)
That will search all students_id where university_id is equal to 3 in the table university_students, all that will match with the table student.
if you only want their name replace * by name like this :
SELECT name FROM student WHERE id IN (SELECT students_id FROM university_students WHERE university_id = 3)
OK got what I wanted, inspired by: https://stackoverflow.com/a/3486662/2396744
Given:
University:
database=> SELECT id FROM university;
id
----
2
3
4
5
(4 rows)
Students:
database=> SELECT id,name FROM students;
id | name
----+----------
4 | Jack
3 | Jill
2 | Jonas
(3 rows)
university_students:
database=> SELECT * FROM university_students;
university_id | students_id
---------------+---------
3 | 3
3 | 2
4 | 4
4 | 2
5 | 3
5 | 2
(6 rows)
The query becomes:
database=> SELECT u.id,us.university_id,us.students_id,s.name
FROM students as s, university as u, university_students as us
WHERE u.id = 5
AND us.university_id = u.id
AND us.student_id = s.id;
id | university_id | student_id | name
----+---------------+------------+----------
5 | 5 | 3 | Jill
5 | 5 | 2 | Jonas
(2 rows)
You can and you really should learn to use JpaRepository of java spring boot, it's easy to use and you can do lot of thing with it, for example i have this class : Client.java
#Entity
public class Client {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
#Column(unique = true, nullable = false) private String email, username;
#Column(unique = true) private String phone;
#Column(nullable = false) private String password, firstName, lastName;
private boolean active;
private long timestampCreation;
private double coefficientReducingBuyer;
private long score;
// User privileges, roles, Address
#ManyToMany(mappedBy = "clients", fetch = FetchType.EAGER) private Set<Privilege> privileges;
#ManyToOne(fetch = FetchType.EAGER) private Role role;
#OneToMany(mappedBy = "client", fetch = FetchType.EAGER, cascade = CascadeType.ALL) private Set<AddressClient> addressClients;
// Market
#OneToMany(fetch = FetchType.EAGER) private Set<Order> orders;
#OneToOne(mappedBy = "client", cascade = CascadeType.ALL) private ShoppingCart shopping;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) private Set<Favorite> favorites;
}
public interface ClientRepository extends JpaRepository<Client, Integer> {
// Find all client by phone number
Client findByPhone(String phone);
// Find all client by phone number which is containing some element of my string
List<Client> findByPhoneIsContaining(String phone);
// Same but ignoring capital letter a and A is the same here
List<Client> findByPhoneIgnoreCaseIsContaining(String phone);
// Find by other table
List<Client> findByRole(Role role);
// Find by other table with a specific value in it
Set<Client> findByPrivileges_Name(String privilege);
}
You don't need to add #Table, only if you want to change the name of your table.
Here my table will name client but i can change is name to user when i do this : #Table(name="user"), so don't use #Table when you don't need it.

JPA Criteria Builder with join and dynamic parameters

I have 2 classes with One-to-Many relationship.
Customer (class) has many Transactions (class)
public class Customer {
#Id
private Long clientId;
private String name;
#OneToMany
private List<Transactions> transactions;
}
public class Transactions {
#JoinColumn(name = "clientId")
private Transactions transactions;
private int statusType;
private String amount;
}
int dynamicValue = 1003;
CriteriaQuery<Customer> criteriaQuery = getBuilder().createQuery(Customer.class);
Root<Customer> customersRoot = criteriaQuery.from(Customer.class);
Join<Customer, Transactions> transactions = customersRoot.join("transactions");
TypedQuery<Customer> query = em.createQuery(criteriaQuery.select(customerRoot).where(getBuilder().equal(transactions.get("statusType"), dynamicValue)));
List<Customer> customerList = (List<Customer>) query.getResultList();
I have 2 data from the DB:
Customer Table
ClientId | Name |
1 | James |
2 | Eli |
Transactions Table:
ClientId | Status Type| Amount| TransactionId |
1 | 1002 | 100 | 1 |
1 | 1003 | 200 | 2 |
I need to make my query above to accept multiple parameters (dynamic). These parameters will be coming from the Customer's attributes such as name, some parameters will be coming from the Transactions class. However, when I tried to execute my code above it always get the 1st record (1002) in my database which is incorrect.
Please give me somelight.
Questions:
How can I achieved to have multiple dynamic parameters in criteria builder?
What is wrong with my query why it always get the 1st record?
You are currently just passing in a Literal. This is not the same as a Parameter. See http://www.datanucleus.org:15080/products/accessplatform_5_2/jpa/query.html#_criteria_api_parameters
Change your code to
CriteriaQuery<Customer> criteriaQuery = getBuilder().createQuery(Customer.class);
Root<Customer> customersRoot = criteriaQuery.from(Customer.class);
Join<Customer, Transactions> transactions = customersRoot.join("transactions");
ParameterExpression param = getBuilder().parameter(int.class, "myParam");
TypedQuery<Customer> query = em.createQuery(criteriaQuery.select(customerRoot).where(getBuilder().equal(transactions.get("statusType"), param)));
// Execute with first parameter value
query.setParameter("myParam", 1003);
List<Customer> customerList = (List<Customer>) query.getResultList();
Then if you get a problem with the result, you look in the JPA providers log at the SQL that was executed, and can understand the problem better

JPA (Hibernate) / QueryDSL left join with condition doesn't work

I have two tables - user and booking. Each user may have many bookings (one-to-many relationship).
user: booking:
id | name | id | country | user_id | price |
-------------| ------------------------------------|
1 | Alice | 1 | Italy | 1 | 2000 |
2 | Bob | 2 | France | 1 | 2500 |
3 | Spain | 1 | 3000 |
I want to select all users and all bookings where booking's price is greater than 2000 using Query DSL. If a user doesn't have any bookings or bookings don't match the condition I still want to select this user.
First, let's have a look at how it would look like using a simple SQL left join query:
SELECT u.*, b.* FROM user u LEFT JOIN booking b ON u.id = b.user_id AND b.price > 2000
The above query should provide the following result:
id | name | id | country | user_id | price |
-------------|----------------------------------------|
1 | Alice | 2 | France | 1 | 2500 |
1 | Alice | 3 | Spain | 1 | 3000 |
2 | Bob | null | null | null | null |
Now I want to do it using JPA with Query DSL
JPA-related stuff:
#Entity
public class User {
#Id
private Long id;
private String name;
#OneToMany(cascade = ALL, fetch = EAGER, orphanRemoval = true, mappedBy = "user")
private List<Booking> bookings;
// getters and setters
}
#Entity
public class Booking {
#Id
private Long id;
private String name;
private Integer price;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "user_id")
private User user;
// getters and setters
}
Query DSL:
public List<User> getUsersAndBookings() {
QUser user = QUser.user;
QBooking booking = QBooking.booking;
JPAQuery<User> jpaQuery = new JPAQuery(entityManager);
List<User> result = jpaQuery.from(user).leftJoin(user.bookings, booking).on(booking.price.gt(2000)).fetchJoin().fetch();
return result;
}
In fact, this code is not working and I get the following exception:
org.hibernate.hql.internal.ast.QuerySyntaxException: with-clause not allowed on fetched associations; use filters [select user from com.example.demo.entity.User user left join fetch user.bookings as booking with booking.price > ?1]
The problem is that the condition clause is specified in on method - on(booking.price.gt(2000)).
After some research I found that this condition should be specified in where method and should look like this:
List<User> result = jpaQuery.from(user).leftJoin(user.bookings, booking).where(booking.price.gt(2000)).fetchJoin().fetch();
This works, but not how I would expect it to work, since it doesn't return ALL users, it returns only one user (Alice), which has some bookings, matching the condition clause. Basically, it just filters the merged table (result table after left join operation) and that's not what I'm looking for.
I want to retrieve all users, and if there are no any bookings for a specific user, then just have null instead of booking list for this user.
Please help, been struggling for hours without any success.
Versions used:
Spring Boot 2.0.2
Spring Data JPA 2.0.7
Hibernate 5.2.16.Final
QueryDSL 4.1.4
You can use isNull expression in where clause to get the rows that have null values.
Your query should be like this:
jpaQuery.from(user)
.leftJoin(user.bookings, booking)
.fetchJoin()
.where(booking.price.gt(2000).or(booking.id.isNull())).fetch();
Hibernate produced query:
select
user0_.id as id1_1_0_,
bookings1_.id as id1_0_1_,
user0_.name as name2_1_0_,
bookings1_.country as country2_0_1_,
bookings1_.price as price3_0_1_,
bookings1_.user_id as user_id4_0_1_,
bookings1_.user_id as user_id4_0_0__,
bookings1_.id as id1_0_0__
from
user user0_
left outer join
booking bookings1_
on user0_.id=bookings1_.user_id
where
bookings1_.id is null
or bookings1_.price>?
It seems there is no JPA way for this. But I got it fixed in Hibernate way, using Filters org.hibernate.annotations.Filter.
#Entity
#FilterDef(name = "anyName", parameters = {
#ParamDef(name = "price", type = "integer")
})
public class User {
#Id
private Long id;
private String name;
#OneToMany(cascade = ALL, fetch = EAGER, orphanRemoval = true, mappedBy = "user")
#Filter(name = "anyName", condition = "price > :inputPrice")
private List<Booking> bookings;
}
Before querying the db, you must enable this filter.
Session session = enityManager.unwrap(Session.class);
session.enableFilter("anyName").setParameter("inputPrice", 2000);
// fetch using hql or criteria; but don't use booking.price.gt(2000) or similar condition there
session.disableFilter("anyName");
Now the result will have a User even if all of his booking prices are below 2000 and bookings list will be empty as expected.
NOTE: The word price in condition should be exactly same as the db column name; not as the model property name.

Hibernate how to add a new row to a table with a foreign key

I am using Java8 with Hibernate and MySQL.
I have the following tables with a join table:
+-------+ +----------------+ +------------+
| job | | person_job | | person |
+-------+ +----------------+ +------------+
| ID | | PER_ID | | ID |
| | | JOB_ID (PK) | +------------+
+-------+ +----------------+
(A PERSON can have many JOBs)
When I try save a new JOB, it has a foreign key join to and existing PERSON. It looks like Hibernate wants to also save a new PERSON, resulting in a duplicate entry. I thought Hibernate would be smart enough, that if there is already a matching PERSON, it won't try save it again.
Resulting in the following error when trying to save a row in the JOB table:
MySQLIntegrityConstraintViolationException: Duplicate entry '338-1688' for key 'PRIMARY'
SQL
SELECT * FROM ebdb.person_job;
PER_ID JOB_ID
338 16
and
SELECT * FROM ebdb.person
ID
338
and
SELECT * FROM ebdb.job;
ID
16
Job.java
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "person_job", joinColumns = {
#JoinColumn(name = "JOB_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
#JoinColumn(name = "PER_ID", referencedColumnName = "ID") })
private Person person;
When I run in debug mode, I see the new Job row it is trying to merge has an ID of 26 and a PERSON with an ID of 338 as expected.
protected T merge(T entity) throws InstantiationException, IllegalAccessException {
T attached = null;
if (entity.getId() != null) {
attached = entityManager.find(entityClass, entity.getId());
}
if (attached == null) {
attached = entityClass.newInstance();
}
BeanUtils.copyProperties(entity, attached);
entityManager.setFlushMode(FlushModeType.COMMIT);
attached = entityManager.merge(attached);
return attached;
}
Question
How do you create a new entry on one table (JOB), that has a foreign key join to an existing entry (PERSON)?
i.e. I want to just maintain a #ManyToOne relationship.
Please can anyone assist.
UPDATE
If I try persist instead of merge, I get:
detached entity passed to persist: com.jobs.spring.domain.Person
SOLUTION
I update with the attached object.
Person attached = entityManager.find(Person.class, person.getId());
person = attached;
job.setPerson(person);

problem with mapping and created tables when using playframework

I tried to create a webapp consisting of shoppingcart and cartitems using playframework.I created the following mappings and when I tried to run the webapp,I found that the postgres db tables created does not have the values ,which creates a bidirectional association.
#Entity
public class CartItem extends Model implements Comparable<CartItem>
#OneToOne
public Product pdt;
#ManyToOne
public ShoppingCart cart;
public int quantity;
...
}
#Entity
public class ShoppingCart extends Model {
#OneToOne
public MyUser customer;
#OneToMany(mappedBy="cart", cascade=CascadeType.ALL)
public Set<CartItem> cartItems;
public ShoppingCart(MyUser customer) {
super();
this.customer = customer;
this.cartItems = new TreeSet<CartItem>();
}
...
}
When I added the cartitem to cart,
public static void addItemToCart(Long productId,Long cartId,String quantity) {
Product pdt = Product.findById(productId);
ShoppingCart cart = ShoppingCart.findById(cartId);
int qty = Integer.parseInt(quantity);
System.out.println("create cartitem from "+qty +" copies of product="+pdt.getName()+" for user="+cart.getCustomer().getEmail());
CartItem cartItem = new CartItem(pdt,qty);
cart.addItem(cartItem);
cart.save();
redirect("/");
}
When this method was executed during post, the println() statement produced
create cartitem from 3 copies of product=Product1 for user=jon#gmail.com
The tables created showed this data
select * from shoppingcart ;
id | customer_id
-----+-------------
191 | 151
(1 row)
select * from cartitem ;
id | quantity | product_id | cart_id
-----+----------+------------+---------
192 | 3 | 168 |
(1 row)
The cart_id column has no value.Is there some problem in the way I defined my mappings? Can someone help me resolve this?
below is the table schema as given by \d in psql
\d cartitem
Table "public.cartitem"
Column | Type | Modifiers
----------+---------+-----------
id | bigint | not null
quantity | integer | not null
product_id | bigint |
cart_id | bigint |
Indexes:
"cartitem_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"fk4393e7353ab523e" FOREIGN KEY (product_id) REFERENCES product(id)
"fk4393e7355396066" FOREIGN KEY (cart_id) REFERENCES shoppingcart(id)
update:
I made this work by
cartItem.setCart(cart);//why do I have to do this?
Now,after I save the cart, the cartitem table has
select * from cartitem;
id | quantity | product_id | cart_id
-----+----------+------------+---------
197 | 4 | 164 | 196
(1 row)
So,I think the bidirectional association doesn't work..
Does someone know why?
Right after
cart.addItem(cartItem);
Try adding
cartItem.cart = cart;
cartItem.save();
because of efficiency,if your ShoppingCart have hundreds of CartItem,every time you invoke save,you must traverse all of them to find which are update,which are add!

Categories