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!
Related
I'm trying to build a select query with Spring Data Specification. The query in question is the following:
SELECT * FROM product WHERE id IN (SELECT product_id FROM product_tags WHERE tags IN ('GRADUATION', 'BIRTHDAY'));
The user is supposed to provide a set of tags to be matched with the IN operator in the subquery, BIRTHDAY and GRADUATION are some examples. I've tried building my solution off this answer but ran into some trouble.
public static Specification<Product> withTags(Set<Tags> tags) {
return tags == null ?
null :
(root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
Subquery<Tags> subquery = query.subquery(Tags.class);
Root<Tags> subqueryRoot = subquery.from(Tags.class);
subquery.select(subqueryRoot.get("product_tags")
.get("product_id"));
subquery.where(criteriaBuilder.trim(subqueryRoot.get("product")
.get("id")).in(tags));
predicates.add(subqueryRoot.get("*").in(subquery));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
The problem here is that I'm trying to create a subquery from Tags which is not registered as an entity but it is rather an enum. Thus, executing the code gives me an error (This is the only error I've encountered so far, please point out parts of the code that may potentially cause other errors).
public enum Tags {
BIRTHDAY("birthday"),
GRADUATION("graduation"),
GET_WELL_SOON("get well soon"),
RIBBON("ribbon"),
WRAPPING_PAPER("wrapping paper");
final String tagName;
private Tags(String tagName) {
this.tagName = tagName;
}
public String getTagName() {
return tagName;
}
}
Not sure if this will help, but in the Product class there is a field tags denoted with #ElementCollection. Spring automatically creates a table named 'product_tags' with this, and the subquery selects from this table.
#ElementCollection(fetch = FetchType.EAGER)
#Enumerated(EnumType.STRING)
private Set<Tags> tags;
If possible, I would like to translate this query instead of the first one
SELECT * FROM product WHERE id IN (SELECT product_id FROM product_tags WHERE tags = ANY(ARRAY['GRADUATION', 'GET_WELL_SOON']));
UPDATE
I have edited my code
public static Specification<Product> withTags(Set<Tags> tags) {
return tags == null ?
null :
(root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
Subquery<Long> subquery = query.subquery(Long.class);
Root<Product> subroot = subquery.from(Product.class);
subquery.select(subroot.get("id").get("tags"));
subquery.where(criteriaBuilder.trim(subroot.join("tags")
.get("id")).in(tags));
predicates.add(root.get("id").in(subquery));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
But now I'm getting this error
java.lang.IllegalStateException: Illegal attempt to dereference path source [null.id] of basic type
For reference, my tables are defined as such
product:
Column | Type | Collation | Nullable | Default
-------------+------------------------+-----------+----------+---------
id | bigint | | not null |
category | character varying(255) | | |
date_added | date | | |
description | character varying(255) | | |
name | character varying(255) | | |
price | double precision | | not null |
product_tags:
Column | Type | Collation | Nullable | Default
------------+------------------------+-----------+----------+---------
product_id | bigint | | not null |
tags | character varying(255) | | |
public static Specification<Product> withTags(Set<Tags> tags) {
return tags.isEmpty() ?
null:
(root, query, criteriaBuilder) -> {
Subquery<Tags> subquery = query.subquery(Tags.class);
Root<Product> subroot = subquery.from(Product.class);
subquery.select(subroot.get("id")).where(subroot.join("tags").in(tags));
Predicate predicate = root.get("id").in(subquery);
return criteriaBuilder.and(predicate);
};
I seemed to have found an answer. Tags.class works apparently, and from there I just had to tweak my query to be a join select. Not what I initially hoped to accomplish, but it works.
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.
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.
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);
I´m trying to join 3 tables with JPA Critera API and get the result as a list of type other than the relation table.
The Entities are:
| Employee | | Contract | | Company |
|----------| |----------| |---------|
| id | | Company | | id |
| age | | Employee | | name |
A Contract is the relationship between a Company and Employee
An employee may belong to one or more Companies
A company has one or more employees
I try now to get all Employees that work for Company A like so:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Contract> query = cb.createQuery(Contract.class);
Root<Contract> contracts = query.from(Contract.class);
Join<Contract, Company> companyJoin = contracts.join("company");
Join<Contract, Employee> employeeJoin = contracts.join("employee");
List<Predicate> conditions = new ArrayList<Predicate>();
conditions.add(cb.equal(companyJoin.get("name"), "Company A"));
TypedQuery<Practice> typedQuery = em.createQuery(query
.select(contracts)
.where(conditions.toArray(new Predicate[conditions.size()]))
);
typedQuery.getResultList();
This gives me a List of Contracts with Empoyees that work in "Company A".
How can I write the Query to get a List of Employees instead of Contracts?
Start with a Root of Employees and make a chain of joins:
CriteriaQuery<Employee> query = cb.createQuery(Employee.class);
Root<Employee> employee = query.from(Employee.class);
Join<Employee, Contract> contractJoin = employee.join("contracts"); // assuming that Employee has a collection property named contracts
Join<Contract, Company> companyJoin = contractJoin.join("company");
This is the correct Awnser with the following addition:
The Types "Employee" and "Company" have to have a field "companies" / "employees" with the #JoinTable annotation like follows:
Employee:
...
#OneToMany
#JoinTable(name="Contract" ...)
private List<Company> companies;
...
Company
...
#OneToMany
#JoinTable(name="Contract" ...)
private List<Employee> employees;
...
The "#JoinTable" annotation prevents hibernate to create a relation table on its own.
See the comments for more info.