Hibernate NOT IN subquery on junction table - java

I'm trying to do a query in hibernate like the following sql query:
SELECT phone.* FROM phone WHERE phone.id NOT IN (SELECT phone_id FROM user_phone)
I have the following entity classes:
#Entity
class User {
#Id
private Integer id;
#ManyToMany
private Set<Phone> phoneList;
}
and the Phone class:
#Entity
class Phone {
#Id
private Integer id;
private String description;
}
Hibernate automatically creates a junction table called user_phone. Now i would like to select all the phones that aren't used by any user. I just cant figure out how to do that with Hibernate. I had tried the following:
Session session = (Session) entityManager.getDelegate();
Criteria criteria = session.createCriteria(Phone.class);
DetachedCriteria subCriteria = DetachedCriteria.forClass(User.class);
subCriteria.setProjection(Property.forName("phoneList"));
criteria.add(Subqueries.propertyNotIn("id", subCriteria))
But that returns all the users where the id is not the same as the id of any of the phones. So that's not what i'm looking for.
Anyone know how to do this?

Criteria criteria = session.createCriteria(Phone.class)
.add(Subqueries.propertyNotIn("id", DetachedCriteria.forClass(User.class)
.createAlias("phoneList", "phone")
.setProjection(Property.forName("phone.id"))
));

Since I reached here looking for how to form a subquery and not criteria, I wonder if other people might end up here the same way, too.
Since I figured out how to write the query in HQL, I wanted to share the solution, just in case:
from phone p where p.id not in (select ph.id from User u join u.phoneList ph)
Worked for me, in a similar scenario. Hope it helps!

Related

How do I access a many-to-many table in jpa?

This is the user class with a #manytomany mapping, I want it to be unidirectional.
#Entity
#Getter
#Setter
#Table(name="users")
public class User implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany
#JoinTable(name="user_drivers", joinColumns=#JoinColumn(name="user_id"), inverseJoinColumns=#JoinColumn(name="driver_id"))
private Set<Driver> driverSet;
public User() {
}
}
A table is created with both keys from user and driver, but I don't know how to access it within my repository.
#Query(value="select u.user_id from user_drivers")
List<?> findAllByIdAndDriver(Long id);
This gives an error: Can't resolve symbol 'user_drivers'
#Query(value="select id,driverSet from User ")
List<?> findAllByIdAndDriver(Long id);
And this results in a nester query exception.
Your method naming is confusing. When you say something like findByIdAndSomethingElse you're implying that you're doing this:
SELECT * FROM MY_TABLE WHERE ID = ? AND SOMETHING_ELSE = ?;
There's missing information here, since you don't share your complete repository or Driver implementations, but assuming you want all user IDs from a Driver with a specific ID, you can simply do this:
#Query("select driver.userId from Driver driver where driver.id = ?1")
public List<Long> findUserIdsByDriverId(long id);
The ?1 is the first argument. You can refer to subsequent arguments with ?2, ?3, ... , ?n
I think the correct queries should be like:
#Query(value="select u.userId from UserDrivers u")
and
#Query(value="select u.id, u.driverSet from User u")
For the 1st query, I'm assuming that entity for the other table is called UserDrivers and its column - userId (according to Java naming conventions).
Found what I needed thanks to the answers pointing me in the right direction.
#Query(value="select u.driverSet from User u where u.id=?1")
List<?> getDriverSet(Long id);

QueryDSL #OneToOne Join-FetchMode with Hibernate

Suppose we have a simple entity "Customer" which has a OneToOne relationship to an entity "Address". The foreign key is on the address side.
#Entity
public class Customer extends EntityBase {
#Column(name = "name", nullable = true)
private String name;
#OneToOne(mappedBy = "customer")
private Address address;
// getter, setter, ...
}
#Entity
public class Address extends EntityBase {
#OneToOne(optional = false)
private Customer customer;
#Column(nullable = true)
private String street;
#Column(nullable = true)
private String zip;
#Column(nullable = true)
private String city;
// getter, setter, ...
}
If you now load all Customer entities using hibernate and print the resulting queries to the console, you can see that Hibernate internally fires only a single query.
session.createCriteria(Customer.class).list();
What Hibernate does:
select
this_.id as id1_1_1_,
this_.name as name2_1_1_,
address2_.id as id1_0_0_,
address2_.city as city2_0_0_,
address2_.customer_id as customer5_0_0_,
address2_.street as street3_0_0_,
address2_.zip as zip4_0_0_
from
Customer this_
left outer join
Address address2_
on this_.id=address2_.customer_id
If you load the Customer entity with QueryDSL it will run one count query (what is expected and okay), one select query for the Customer entity and one query for each customer in the resultset. This means, if I want to load 1000 customers it will run 1002 SQL queries. This is a lot of network traffic and slows down the application.
new HibernateQuery<Customer>(session).from(QCustomer.customer).fetchResults();
What Hibernate with QueryDSL does:
select
count(customer0_.id) as col_0_0_
from
Customer customer0_
select
customer0_.id as id1_1_,
customer0_.name as name2_1_
from
Customer customer0_
select
address0_.id as id1_0_1_,
address0_.city as city2_0_1_,
address0_.customer_id as customer5_0_1_,
address0_.street as street3_0_1_,
address0_.zip as zip4_0_1_,
customer1_.id as id1_1_0_,
customer1_.name as name2_1_0_
from
Address address0_
inner join
Customer customer1_
on address0_.customer_id=customer1_.id
where
address0_.customer_id=?
Question:
Is it possible to set something like a global FetchMode for QueryDSL queries. In Hibernate you can specify this with #Fetch(FetchMode.JOIN) but unfortunately this is ignored by QueryDSL.
So my destination is to load 1000 customers with QueryDSL and only run 2 queries (count + select).
I already know that there is a way to specify something like this:
new HibernateQuery<Customer>(session)
.from(QCustomer.customer)
.leftJoin(QCustomer.customer.address).fetchJoin()
.fetchResults();
But this is error-prone because you have to specify it in every query and I don't want to declare every join by myself. QueryDSL already does it automatically when using predicates:
new HibernateQuery<Customer>(session)
.from(QCustomer.customer)
.where(QCustomer.customer.address.street.in("Musterstraße 12"))
.fetchResults();
So I want to use the above expression to load my customers, but I don't want to fire thousands of requests to my database and I also don't want to declare every join by myself. Is this possible?
I pushed an example project here: https://github.com/MatWein/testproject
Once I have the same problem. But i am working with Hibernate JPA criteria Query.
If you want to get your result in one Query then one way Is to use
#OneToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "address_id", insertable = false, updatable = false, referencedColumnName = "id")
private Address address;
Or i have a solution with Criteria Query. May be it helps you to convert it into DSL.
create a root of Customer class.
Root<Customer> root = . . .
Join<Customer, Address> join = (Join<Customer, Address>)root.fetch(Customer_.address);
for reference see my Question

Hibernate: projection JPQL query to DTO issue

To start with, I'll list three models that I work with in a query
ProductEntity:
#Entity
#Table(name = "product")
public class ProductEntity extends BaseEntity {
//some fields
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "owner_id")
private PartnerEntity owner;
#OneToMany(
mappedBy = "product",
fetch = FetchType.LAZY
)
private List<StockProductInfoEntity> stocks;
}
PartnerEntity:
#Entity
#Table(name = "partner")
public class PartnerEntity extends AbstractDetails {
//some fields
#OneToMany(
mappedBy = "owner",
fetch = FetchType.LAZY
)
private List<ProductEntity> products;
}
and StockProductInfoEntity:
#Entity
#Table(name = "stock_product")
public class StockProductInfoEntity extends BaseEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "product_id")
private ProductEntity product;
//other fields
#Column(name = "rest")
private int rest;
}
And i want to fetch from database product with partner + calculate count in all stocks.
For convenience, I created a simple DTO:
#Getter
#AllArgsConstructor
public class ProductCountDTO {
private ProductEntity productEntity;
private int count;
//hack for hibernate
public ProductCountDTO(ProductEntity productEntity, long count) {
this.productEntity = productEntity;
this.count = (int) count;
}
}
and write JPQL query in JPA repository:
#Query("select new ru.oral.market.persistence.entity.product.util.ProductCountDTO(p, sum(stocks.rest))"+
" from ProductEntity p" +
" join fetch p.owner owner" +
" join p.stocks stocks" +
" where p.id = :id" +
" group by p, owner")
Optional<ProductCountDTO> findProductWithCount(#Param("id") long id);
But my application did not even start because of a problem with the query validation. I get this message:
Caused by: org.hibernate.QueryException: query specified join
fetching, but the owner of the fetched association was not present in
the select list
Very strange, but I tried to replace join fetch -> join.
And I understood why I got this error, hibernate made such a query to the database:
select
productent0_.id as col_0_0_,
sum(stocks2_.rest) as col_1_0_
from
product productent0_
inner join
partner partnerent1_
on productent0_.owner_id=partnerent1_.user_id
inner join
stock_product stocks2_
on productent0_.id=stocks2_.product_id
where
productent0_.id=?
group by
productent0_.id ,
partnerent1_.user_id
But why does he only take the product id and nothing else?
This query with Tuple work and get all fields from product and partner
#Query("select p, sum(stocks.rest) from ProductEntity p" +
" join fetch p.owner owner" +
" join p.stocks stocks" +
" where p.id = :id" +
" group by p, owner")
Optional<Tuple> findProductWithCount(#Param("id") long id);
And this produced native query what i want:
select
productent0_.id as col_0_0_,
sum(stocks2_.rest) as col_1_0_,
partnerent1_.user_id as user_id31_12_1_,
productent0_.id as id1_14_0_,
productent0_.brand_id as brand_i17_14_0_,
productent0_.commission_volume as commissi2_14_0_,
productent0_.created as created3_14_0_,
productent0_.description as descript4_14_0_,
productent0_.height as height5_14_0_,
productent0_.length as length6_14_0_,
productent0_.long_description as long_des7_14_0_,
productent0_.name as name8_14_0_,
productent0_.old_price as old_pric9_14_0_,
productent0_.owner_id as owner_i18_14_0_,
productent0_.pitctures as pitctur10_14_0_,
productent0_.price as price11_14_0_,
productent0_.status as status12_14_0_,
productent0_.updated as updated13_14_0_,
productent0_.vendor_code as vendor_14_14_0_,
productent0_.weight as weight15_14_0_,
productent0_.width as width16_14_0_,
partnerent1_.about_company as about_co1_12_1_,
partnerent1_.bik as bik2_12_1_,
partnerent1_.bank_inn as bank_inn3_12_1_,
partnerent1_.bank_kpp as bank_kpp4_12_1_,
partnerent1_.bank as bank5_12_1_,
partnerent1_.bank_address as bank_add6_12_1_,
partnerent1_.checking_account as checking7_12_1_,
partnerent1_.correspondent_account as correspo8_12_1_,
partnerent1_.company_name as company_9_12_1_,
partnerent1_.company_inn as company10_12_1_,
partnerent1_.company_kpp as company11_12_1_,
partnerent1_.ogrn as ogrn12_12_1_,
partnerent1_.okato as okato13_12_1_,
partnerent1_.actual_address as actual_14_12_1_,
partnerent1_.director as directo15_12_1_,
partnerent1_.full_name as full_na16_12_1_,
partnerent1_.legal_address as legal_a17_12_1_,
partnerent1_.short_name as short_n18_12_1_,
partnerent1_.country as country19_12_1_,
partnerent1_.discount_conditions as discoun20_12_1_,
partnerent1_.discounts as discoun21_12_1_,
partnerent1_.logo as logo22_12_1_,
partnerent1_.min_amount_order as min_amo23_12_1_,
partnerent1_.min_shipment as min_shi24_12_1_,
partnerent1_.min_sum_order as min_sum25_12_1_,
partnerent1_.own_delivery as own_del26_12_1_,
partnerent1_.own_production as own_pro27_12_1_,
partnerent1_.phones as phones28_12_1_,
partnerent1_.return_information as return_29_12_1_,
partnerent1_.site as site30_12_1_
from
product productent0_
inner join
partner partnerent1_
on productent0_.owner_id=partnerent1_.user_id
inner join
stock_product stocks2_
on productent0_.id=stocks2_.product_id
where
productent0_.id=?
group by
productent0_.id ,
partnerent1_.user_id
But it's not very convenient.
Why DTO projection doesn't work correctrly, but tuple works fine?
Because that's how Hibernate is currently implemented.
Because you used an entity in the DTO Projection, which as the name implies, it should be used for DTOs, not entities, Hibernate is going to assume that you want to GROUP BY by the identifier because it should not GROUP BY all entity properties.
The Tuple is broken and it will only work in MySQL, but not in Oracle or PostgreSQL since your aggregate query selects columns that are not present in the GROUP BY clause.
However, this is not demanded to work according to the JPA specs. Nevertheless, you should still provide a replicating test case and open an issue so that the behavior is the same for both situations.
Anyway, once fixed, it will still GROUP BY identifier. If you want to select entities and group by as well, you will have to use a native SQL query along with the Hibernate ResultTransformer to transform the ResultSet into a graph of objects.
More, fetching entities and aggregations is a code smell. Most likely, you need a DTO projection or a read-only view.
Entities should only be fetched when you want to modify them. Otherwise, a DTO projection is more efficient and more straightforward as well.
Since Vlad already explained the why, I will focus on an alternative solution. Having to specify all attributes that you are really interested in in the SELECT clause and the GROUP BY clause is a lot of work.
If you used Blaze-Persistence Entity Views on top of Hibernate, this could look like the following
#EntityView(ProductEntity.class)
public interface ProductCountDTO {
// Or map the ProductEntity itself if you like..
#Mapping("this")
ProductView getProduct();
#Mapping("sum(stocks.rest)")
int getCount();
}
#EntityView(ProductEntity.class)
public interface ProductView {
// Whatever mappings you like
}
With the Spring Data or DeltaSpike Data integration you can even use it like that
Optional<ProductCountDTO> findById(long id);
It will produce a JPQL query like the following
SELECT
p /* All the attributes you map in ProductView */,
sum(stocks_1.rest)
FROM
ProductEntity p
LEFT JOIN
p.stocks stocks_1
GROUP BY
p /* All the attributes you map in ProductView */
Maybe give it a shot? https://github.com/Blazebit/blaze-persistence#entity-view-usage
The magic is that Blaze-Persistence handles the GROUP BY automatically when encountering an aggregate function by putting every non-aggregate expression you use into the GROUP BY clause if there is at least one aggregate function used.
When using Entity Views instead of entities directly, you won't be facing the join fetch problems as Entity Views will only put the fields you actually map into the resulting SELECT clause of the JPQL and SQL.
Even if you used entities directly or via the ProductCountDTO, the query builder used behind the scenes handles selects of entity types in case of a group by gracefully, just as you'd expect it from Hibernate.

Hibernate query find if ids exist on some entity.list.ids

I have such case trying to query some ids if they exist on some entity list ex :
#Entity
class Person{
#ManyToMany(fetch = FetchType.EAGER)
List<Job> jobList;
}
#Entity
class Job{
#Id
long id;
//otherfields...
}
Query trying to do :
From Person p where :selectedJobIds exists in :(p.jobList.id)
So I'm trying to find if each Job of person's jobs has id found in selectedJobIds
Is it doable on Hibernate or Am I forced to use subquery tried using elements with no luck
You can do this in hibernate by using Criteria API too. Use add() method present for Criteria object to add restrictions for a criteria query.
For example:
Criteria cr= session.createCriteria(Job.class);
cr.add(Restrictions.eq("id",2);
List results= cr.list();

How to make left join on two parameters in Ebean?

I have two tables: "users" and "mail_list" with corresponding classes.
These tables are connected with the help of foreign key user_id (in mail_list table) that references id (in users table). Users can have records of two kinds in mail_list table - 'general' or/and 'admin'. If user has a record in mail_list table, this means that he doesn't want to recieve mails of corresponding kind.
I'd like to find all users who want to recieve mails of general kind. I'm sure that the right SQL query looks like this:
SELECT U.id, U.email, M.user_id, M.kind
FROM users U
LEFT JOIN mail_list M
ON (U.id = M.user_id AND M.kind = 'general')
WHERE M.user_id IS NULL
But unfortunately I'm not so good with Ebean. Could you, please, help me to write such a Ebean query if it is possible? I'd like to avoid using Raw SQL.
Here, also, some code of my classes is:
#Entity
#Table(name = "users")
public class User {
#Id
public Long id;
public String email;
#OneToMany(mappedBy = "user")
public List<MailList> mailLists;
}
#Entity
#Table(name = "mail_list")
public class MailList {
#Id
public Long id;
/**
* Kind of mail list
*/
public String kind;
public static String GENERAL = "general";
public static String ADMIN = "admin";
#ManyToOne
public User user;
}
I use PlayFramework 2.2.3.
My solution to your problem is:
List<MailList> mailList = MailList.find.where().like("kind", "general").findList();
Set<User> userSet = new HashSet<User>();
for(MailList mail:mailList)
userSet.add(mail.user);
It finds mailing lists that fulfill search criteria. Then it creates set of users.
I think this is what you are looking for:
Finder<Long, User> finder = new Finder<Long, User>(Long.class, User.class);
List<User> users = finder.fetch("mailLists").where().eq("mailLists.kind", "general").findList();
This piace of code will generate the following query:
SELECT U.id, U.email, M.user_id, M.kind
FROM users U
LEFT JOIN mail_list M ON U.id = M.user_id
WHERE M.kind = 'general';
I suggest you to use enum instead of static strings. This will be better to reference on your source code.
The unique part that I didn't understood on your question is the part that you use a field to join a table but on the where you filter for null values of that field.

Categories