Java EE Query two tables - java

So here is my problem:
I have two tables: User and Book, they are in ManyToOne relation. The Book table has attribute called user_id that connects both tables.
Using Eclipse I generated entity classes and work on them without problem until now.
The problem is, when I want to get "books" that have speciffic user_id I get an error:
java.lang.IllegalArgumentException: Parameter value [1] did not match expected type [model.User (n/a)]
The "value" is an id that I'm getting from session, I tried it in both int and String.
part of BookDao:
public List<Book> getFullListWithId(Integer id) {
List<Book> list = null;
Query query = em.createQuery("select b from Book b WHERE b.user = :id");
if (id!= null) {
query.setParameter("id", id);
}
try {
list = query.getResultList();
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
part of BookBB:
public List<Book> getFullListWithId(){
HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(true);
Integer str =(Integer)session.getAttribute("userId");
return bookDAO.getFullListWithId(str);
}
part of Book.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id_offer")
private int idOffer;
private String adress;
private String contact;
#Column(name="is_ready")
private String isReady;
private String name;
private String others;
private int price;
private int rooms;
private String size;
private String surname;
//bi-directional many-to-one association to User
#ManyToOne
#JoinColumn(name="user_id")
private User user;
part of User.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id_user")
private int idUser;
private String login;
private String password;
//bi-directional many-to-one association to Book
#OneToMany(mappedBy="user")
private List<Book> books;
Thank you so much for any help possible.

You have two options.
1) you pass an User object with the id as a parameter to the query (I prefer that solution):
Query query = em.createQuery("select b from Book b WHERE b.user = :user");
User u = new User();
u.setId(id)
query.setParameter("user", u);
2) you pass the id as a parameter:
Query query = em.createQuery("select b from Book b WHERE b.user.id = :id");
if (id!= null) {
query.setParameter("id", id);
}
Please note the b.user.id in the query

I believe query should be select b from Book b WHERE b.user.idUser = :id
You might want to take a look at hibernate docs
https://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/queryhql.html {Chapter 16}

Related

Convert postgresql join and group by query to JPA criteria API

I am having trouble to converting the following postgresql query (with a join and a group by) to JPA criteria API for a Spring Boot, JPA, Hibernate application:
select u.id, u.full_name, count(*) project_applications_count from users u
join project_applications pa on pa.created_by = u.id
group by u.id, u.full_name
having count(*) >= 1 and count(*) <= 5
The tables look like this:
create table project_applications (
id serial primary key,
...
city_id integer not null references cities (id),
created_by integer not null references users (id)
);
create table users (
id serial primary key,
...
full_name varchar(100) not null
);
And the entities look like this:
#Entity
#Table(name = "project_applications")
public class ProjectApplication {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "created_by")
private User createdBy;
...
}
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "full_name")
private String fullName;
...
}
I tried searching online for a solution but every exemple I found was using either a join or group by, but not both.
Using #akortex's idea with projections, I think something like this should work:
public class UserSummary {
private Long id;
private String fullName;
private Long count;
public UserSummary() {
}
public UserSummary(Long id, String fullName, Long count) {
this.id = id;
this.fullName = fullName;
this.count = count;
}
... (getters and setters)
}
public List<UserSummary> getSummaries(Integer minProjectAppsCount, Integer maxProjectAppsCount) {
CriteriaBuilder cb = _entityManager.getCriteriaBuilder();
CriteriaQuery<UserSummary> query = cb.createQuery(UserSummary.class);
Root<ProjectApplication> projectApp = query.from(ProjectApplication.class);
Join<ProjectApplication, User> userJoin = projectApp.join("createdBy", JoinType.INNER);
query.multiselect(userJoin.get("id"), userJoin.get("fullName"), cb.count(projectApp))
.groupBy(userJoin.get("id"), userJoin.get("fullName"));
List<Predicate> predicates = new ArrayList<>();
if (minProjectAppsCount != null ) {
Predicate p = cb.ge(cb.count(projectApp), minProjectAppsCount);
predicates.add(p);
}
if (maxProjectAppsCount != null ) {
Predicate p = cb.le(cb.count(projectApp), maxProjectAppsCount);
predicates.add(p);
}
query.having(predicates.toArray(new Predicate[0]));
return _entityManager.createQuery(query).getResultList();
}
You could potentially look into projections in order to achieve what you want.
For example consider the following projection and repository:
#Data
#AllArgsConstructor
public class ProjectApplicationSummary {
private Long id;
private String fullName;
private Long count;
}
And:
#Repository
public interface ProjectApplicationRepository extends JpaRepository<ProjectApplication, Long> {
#Query(
"""
SELECT new com.example.springdemo.entities.ProjectApplicationSummary(u.id, u.fullName, count(pa))
FROM User u, ProjectApplication pa
GROUP BY u.id, u.fullName
"""
)
List<ProjectApplicationSummary> getSummaries();
}
You will most likely need to tweak the query a bit (which revolves experimenting with JPQL) but other than that, the basic idea is there.
I'm not sure in my solution, but it should be similar. I took an idea from here. Maybe it helps you to resolve your problem.
public static Specification<User> getUsers() {
return Specification.where((root, query, criteriaBuilder) -> {
CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
Subquery<Long> subQuery = criteriaQuery.subquery(Long.class);
Root<ProjectApplication> subRoot = subQuery.from(ProjectApplication.class);
subQuery
.select(criteriaBuilder.count(subRoot))
.where(criteriaBuilder.equal(root.get("id"), subRoot.get("createdBy").get("id")));
query
.multiselect(criteriaBuilder.construct(root.get("id"), root.get("fullName")))
.groupBy(root.get("id"), root.get("fullName"))
.having(criteriaBuilder.and(
criteriaBuilder.greaterThanOrEqualTo(subQuery.getSelection(), 1L),
criteriaBuilder.lessThanOrEqualTo(subQuery.getSelection(), 5L)));
return query.getRestriction();
});
}

DTO Query with JPA CriteriaQuery and Hibernate using entity as a DTO constructor creates many selects

hibernate 5.2.10.Final
jpa 2.1
I want to map a projection query to a DTO (Data Transfer Object) with JPA Criteria Query and Hibernate. I specify a constructor that will be applied to the results of the query execution.
If the constructor is for entire entity class, I have multiple of selects instead one(it is a long running process for thousands of records). If the constructor is for a set of params of the Entity then I see only one select in the console. I can't understand where I've mistaken or is it a bug?
public class ServiceDAO {
public List<ServicesDTO> getAllServicesByFilter(ServicesFilter filter) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ServicesDTO> criteria = cb.createQuery(ServicesDTO.class);
Root<ServicesEntity> serviceEntity = criteria.from(ServicesEntity.class);
// here is only one select to get list of services
criteria.select(cb.construct(ServicesDTO.class, serviceEntity.get("active"), serviceEntity.get("providerId"), serviceEntity.get("serviceId")));
// in this case I have multiple selects
//criteria.select(cb.construct(ServicesDTO.class, serviceEntity));
if(filter != null) {
List<Predicate> pcl = new ArrayList<Predicate>();
if(filter.getActive() != null)
pcl.add(cb.equal(serviceEntity.get("active"), filter.getActive()));
if(filter.getProviderId() != null)
pcl.add(cb.equal(serviceEntity.get("providerId"), filter.getProviderId()));
if(filter.getServiceId() != null)
pcl.add(cb.equal(serviceEntity.get("serviceId"), filter.getServiceId()));
criteria.where(pcl.toArray(new Predicate[pcl.size()]));
}
return entityManager.createQuery(criteria).getResultList();
}
}
-
public class ServicesDTO implements Serializable {
private static final long serialVersionUID = 1L;
private Boolean active;
private Integer providerId;
private Integer serviceId;
public ServicesDTO() {}
public ServicesDTO(Boolean active, String providerId, Integer serviceId) {
this.active = active;
this.providerId = Integer.parseInt(providerId);
this.serviceId = serviceId;
}
public ServicesDTO(ServicesEntity service) {
if(service != null) {
this.active = service.isActive();
this.providerId = Integer.parseInt(service.getProviderId());
this.serviceId = service.getServiceId();
}
// getters & setters
}
-
#Entity
#Table
public class ServicesEntity {
#Id
#Column(name = "id", unique = true)
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name = "serviceId", nullable = false)
private int serviceId;
#Column(nullable = false)
private String providerId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="categoryId")
private Categories categoryId;
private boolean active;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "service", cascade = CascadeType.ALL)
private List<Service_Area_Ref> areas = new ArrayList<Service_Area_Ref>();
#ManyToOne(fetch=FetchType.LAZY, optional = true)
#JoinColumn(name="parentCatId")
private Categories parentCatId;
public ServicesEntity() {}
public ServicesEntity(int serviceId) {
this.serviceId = serviceId;
}
// getters & setters
// equals & hashcode
}
Yea, so it does. There is probably not much of a use case for that. Given
#Entity
public class A {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private Integer value;
public class ADto {
private Integer va;
public ADto(A a) {
this.va = a.getValue();
}
public ADto(Integer va) {
this.va = va;
}
Then
tx.begin();
A a1 = new A();
a1.setValue(1);
A a2 = new A();
a1.setValue(2);
em.persist(a1);
em.persist(a2);
tx.commit();
em.clear();
System.out.println("As usual");
em.createQuery("select new dto.ADto(a.value) from A a where a.value <= 2", ADto.class).getResultList();
System.out.println("As A");
em.createQuery("select new dto.ADto(a) from A a where a.value <= 2", ADto.class).getResultList();
gives you
create table A (id integer generated by default as identity (start with 1), value integer, primary key (id))
create table B (id integer generated by default as identity (start with 1), value integer, primary key (id))
insert into A (id, value) values (default, ?)
insert into A (id, value) values (default, ?)
As usual
select a0_.value as col_0_0_ from A a0_ where a0_.value<=2
As A
select a0_.id as col_0_0_ from A a0_ where a0_.value<=2
select a0_.id as id1_0_0_, a0_.value as value2_0_0_ from A a0_ where a0_.id=?
select a0_.id as id1_0_0_, a0_.value as value2_0_0_ from A a0_ where a0_.id=?
And you don't like the fact that entity A is selected each time for a new ADto instance. It's probably done that way because you could have created a DTO with multiple entities, not just A, like A, B, and C and so how would JPA/Hibernate do that conveniently in a single select statement? While it could select all the attributes and then keep track of which attributes belong to which entities and then construct them and pass them to your DTO so you can deconstruct them that seems like a lot of work for a rare thing. It's probably more efficient and better all around if you select the attributes you want and make a constructor out of whatever that is, as in the first case.
I am using Hibernate 5.3 and also encounter this behaviour. But I found that if using JPA Tuple as a DTO container and multiselect, this problem will not happen. So my final solution is use Tuple to query the result set first and then convert it to DTO manually , something likes:
CriteriaQuery<Tuple> criteria = cb.createTupleQuery();
.......
criteria.multiselect(serviceEntity);
List<ServicesDTO> result = entityManager.createQuery(criteria).getResultList().stream()
.map(t->new ServicesDTO(t.get(0,ServicesEntity.class)))
.collect(toList());

JPA query for getting list of objects which are in one To Many relationship

I have following tables in one to many relationship
Class A {
private Integer id;
private String name;
#OneToMany(mappedBy="a", fetch=FetchType.LAZY)
private List<B> bs;
public A(Integer id, String name, List<B> bs){
this.id = id;
this.name = name;
this.bs = bs;
}
}
Class B {
#ManyToOne
private A a;
private String name;
}
I wanto to write query to get data of class A along with their bs
e.g
Select NEW A(a.id, a.name, a.bs) FROM A a WHERE a.id = 10;
my constructor is of parameter (Integer id, String name, List bs). But it throws error unnable to locate appropriate constructor.
Can you tell me what mistake I am doing. And is this really possible in JPA
I think that you missed the #JoinColumn annotation of JPA. Try this:
Class A {
private Integer id;
private String name;
#OneToMany(mappedBy="a", fetch=FetchType.LAZY)
private List<B> bs;
}
Class B {
#ManyToOne
#JoinColumn(name="aId") // Link 'aId' foreign key <--
private A a;
private String name;
}
Give the full package name of the class new A in your query.
First you need to add the default constructor to you entities
The query is better like this
"Select NEW A(a.id, a.name, a.bs) FROM " + A.class.getSimpleName() + " a WHERE a.id = 10";
Also as msagala said you need to the #JoinColumn(name = "id")

#OneToMany which is in fact OneToOne / #Formula with Parameters

I have a Table
Products ( ProductId, Name ) and
ProductPrices (ProductId, Market, Price)
ProductPrices has a compositeKey (ProductId, Market). For a given Market, a Product has 0..1 Prices in that Market.
First approach #Formula
The Market is known at runtime, and can possibly be changed per request.
In an first attempt to model the ProductEntity I took an #Formula annotation, like so:
#Entity
#Table(...)
public class Product {
#Id
private int ProductId;
private String name;
#Formula("(SELECT TOP 1 Price FROM ProductPrices p WHERE p.ProductId = ProductId AND p.Market='Berlin')")
private double price;
}
But obviously, the market is then hard-compiled as annotations need to be static final Strings. [ so no #Formula("..." + getCurMarket() ) ].
Second approach, #OneToMany
Take a separate entity class for the prices, and reference them in the product entity as:
#OneToMany(mappedBy = "product")
private List<Price> price;
In a getPrice(), I could always return the first entry (there will never be more...) or nothing if the list is empty.
I then want to create a Predicate/Specification to use from within the ProductService. Example:
public static Specification<Product> marketEquals(final String market) {
return new Specification<Product>() {
#Override
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<String> q = cb.createQuery(String.class);
Root<Price> price = q.from(Price.class);
return price.get("Market").in("Berlin");
}
};
}
However, that only results in a (and I tried writing "market", "Market", ...)
org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.market' [select generatedAlias0 from ...backend.entities.Product as generatedAlias0 where generatedAlias1.market in (:param0)]
Third approach, Hibernate/JPA Filter
This time, I write in the product entity
#OneToMany(mappedBy = "product")
#Filters( {
#Filter(name="marketFilter", condition="Market = :market")
} )
private List<Price> price;
Again, I want to fill this filter in the ProductService, but I cannot gelt hold of the CurrentSession. I tried the Spring-way, adding an #Autowired private SessionFactory sessionFactory; and configuring it through
Filter filter = sessionFactory.getCurrentSession().enableFilter("marketFilter");
filter.setParameter("market", "Berlin" );
but I cannot get hold of the right context, as org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.hibernate.SessionFactory
Who could advise on how to model the database schema as entities, or could point working solutions to approach 2 and 3 ? Thanks!
Second approach is actually correct.Try it changing your entities and creteria query.
Products table:
#Entity
#Table(...)
public class Product {
#Id
private int ProductId;
private String name;
#OneToMany(mappedBy = "products")
private List<ProductPrices> ProductPrices;
}
ProductPrices table:
#Entity
#Table(...)
public class ProductPrices {
#Id
private int ProductPriceId;
private String market;
private double price;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "product_id") //foreign key reference
private Product products
}
ProductService:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Product> qry = cb.createQuery(Product.class);
Root<Product> root = qry.from(Product.class);
Join<Product, ProductPrices> price = root.join("ProductPrices");
List<Predicate> conditions = new ArrayList<>();
conditions.add(cb.equal(price.get("products"), "Berlin"));
TypedQuery<Product> typedQuery = em.createQuery(qry
.select(root)
.where(conditions.toArray(new Predicate[] {}))
.orderBy(cb.asc(root.get("Berlin")))
.distinct(true)
);
return typedQuery;

JPA delete bidirectional entity

I am having some problems with JPA. I am new at this topic so my question maybe is really stupid, but i hope some of you could point me to the right direction.
I have Project and User entity. Every user can have as many projects assign to him as it can.
I created the relationship bidirectional
User OneToMany -> Project,
Project ManyToOne -> User
My problem is that if i want to delete a user i want all the projects to be deleted as well, but i receive an error at that point:
Internal Exception: java.sql.SQLIntegrityConstraintViolationException:
DELETE on table 'USER_DATA' caused a violation of
foreign key constraint 'PROJECT_USERNAME' for key (Test Use1312r1).
The statement has been rolled back.
Error Code: -1
Call: DELETE FROM USER_DATA WHERE (USERNAME = ?)
bind => [1 parameter bound]
My User entity looks like this:
#Entity
#Table(name="USER_DATA", uniqueConstraints = #UniqueConstraint(columnNames = {"USERNAME", "link"}))
public class User implements Serializable {
#Column(name="USERNAME")
#Id
#NotNull
private String name;
#Column(name="USERROLE")
#Enumerated(EnumType.STRING)
private UserRole role;
private String password;
private String link;
// Should be unique
private String session;
#OneToMany(mappedBy="user", cascade=CascadeType.ALL)
private Collection<Project> projects;
My Project Entity like this:
#Entity
#Table(name="PROJECT")
#XmlRootElement
public class Project implements Serializable {
#Id
private int id;
private String name;
private String description;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="START_DATE")
private Date beginDate;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="END_DATE")
private Date endDate;
#ManyToOne
#JoinColumn(name="USERNAME", nullable=false,updatable= true)
private User user;
And my BL:
public User getUser(String userName) throws NoDataFoundException {
EntityManager em = DbConnection.getInstance().getNewEntity();
try {
User user = em.find(User.class, userName);
if (user == null) {
throw new NoDataFoundException("User is not found in the DB");
}
return user;
} finally {
em.close();
}
}
public void deleteUser(String userName) throws ModelManipulationException {
EntityManager em = DbConnection.getInstance().getNewEntity();
try {
User userToBeDeleted = getUser(userName);
em.getTransaction().begin();
userToBeDeleted = em.merge(userToBeDeleted);
em.remove(userToBeDeleted);
em.getTransaction().commit();
} catch (Exception e) {
throw new ModelManipulationException(
"Error in deleting user data for username" + userName
+ "with exception " +e.getMessage(),e);
}
finally{
em.close();
}
}
Thanks in advance guys.
after the merge call, are there any Projects in userToBeDeleted.projects? I suspect there are none, which prevents any from being deleted. Cascade remove can only work if you populate both sides of bidirectional relationships, so check that when you associate a user to a project, you also add the project to the user's project collection.

Categories