Personalized Query JPA - java

I'm creating a project manager, where the user can have multiple projects, but a project can only belong to one user (#OnetoMany). I managed to make the relationship and everything, the problem is that I wanted to make a custom query displaying all the projects of only one user. Could you give me an idea of ​​how to do this custom query, please?
Relationship in User Class
#OneToMany(cascade = CascadeType.ALL) #JoinColumn(name = "id_user") private List<Project> projects = new ArrayList<>();
ProjectController Class
#GetMapping("/listar/{userId}") public ResponseEntity<List<Project>> listProjectsByUser(#PathVariable Long userId) { return ResponseEntity.ok(projectService.listProjectsByUser(userId));}
ProjectService Class
public List<Project> listProjectsByUser(Long userId) { return projectRepository.findProjectByUser(userId);}
Finally the #Query created in ProjectRepository Class
#Query("SELECT p FROM Project p WHERE p.id = :id_user") List<Project> findProjectByUser(Long id);
This is the Projects table that is being generated by JPA, notice that I am trying to do a "SELECT" using the column "id_user"
I tried make this Query, but it gives this error
Named parameter not bound : id_user; nested exception is org.hibernate.QueryException: Named parameter not bound : id_user

u can try this way
replace id_user -> id
#Query("SELECT p FROM Project p WHERE p.id = :id")
List<Project> findProjectByUser(Long id);
or replace id -> id_user if u want
#Query("SELECT p FROM Project p WHERE p.id = :id_user")
List<Project> findProjectByUser(Long id_user);
I hope it works

Related

Is there a way to map 1 column of a query to a collection using Spring JPA/Hibernate [duplicate]

I am using Spring JPA to perform all database operations. However I don't know how to select specific columns from a table in Spring JPA?
For example:
SELECT projectId, projectName FROM projects
You can use projections from Spring Data JPA (doc). In your case, create interface:
interface ProjectIdAndName{
String getId();
String getName();
}
and add following method to your repository
List<ProjectIdAndName> findAll();
I don't like the syntax particularly (it looks a little bit hacky...) but this is the most elegant solution I was able to find (it uses a custom JPQL query in the JPA repository class):
#Query("select new com.foo.bar.entity.Document(d.docId, d.filename) from Document d where d.filterCol = ?1")
List<Document> findDocumentsForListing(String filterValue);
Then of course, you just have to provide a constructor for Document that accepts docId & filename as constructor args.
You can set nativeQuery = true in the #Query annotation from a Repository class like this:
public static final String FIND_PROJECTS = "SELECT projectId, projectName FROM projects";
#Query(value = FIND_PROJECTS, nativeQuery = true)
public List<Object[]> findProjects();
Note that you will have to do the mapping yourself though. It's probably easier to just use the regular mapped lookup like this unless you really only need those two values:
public List<Project> findAll()
It's probably worth looking at the Spring data docs as well.
In my situation, I only need the json result, and this works for me:
public interface SchoolRepository extends JpaRepository<School,Integer> {
#Query("select s.id, s.name from School s")
List<Object> getSchoolIdAndName();
}
in Controller:
#Autowired
private SchoolRepository schoolRepository;
#ResponseBody
#RequestMapping("getschoolidandname.do")
public List<Object> getSchool() {
List<Object> schools = schoolRepository.getSchoolIdAndName();
return schools;
}
With the newer Spring versions One can do as follows:
If not using native query this can done as below:
public interface ProjectMini {
String getProjectId();
String getProjectName();
}
public interface ProjectRepository extends JpaRepository<Project, String> {
#Query("SELECT p FROM Project p")
List<ProjectMini> findAllProjectsMini();
}
Using native query the same can be done as below:
public interface ProjectRepository extends JpaRepository<Project, String> {
#Query(value = "SELECT projectId, projectName FROM project", nativeQuery = true)
List<ProjectMini> findAllProjectsMini();
}
For detail check the docs
In my case i created a separate entity class without the fields that are not required (only with the fields that are required).
Map the entity to the same table.
Now when all the columns are required i use the old entity, when only some columns are required, i use the lite entity.
e.g.
#Entity
#Table(name = "user")
Class User{
#Column(name = "id", unique=true, nullable=false)
int id;
#Column(name = "name", nullable=false)
String name;
#Column(name = "address", nullable=false)
Address address;
}
You can create something like :
#Entity
#Table(name = "user")
Class UserLite{
#Column(name = "id", unique=true, nullable=false)
int id;
#Column(name = "name", nullable=false)
String name;
}
This works when you know the columns to fetch (and this is not going to change).
won't work if you need to dynamically decide the columns.
In my opinion this is great solution:
interface PersonRepository extends Repository<Person, UUID> {
<T> Collection<T> findByLastname(String lastname, Class<T> type);
}
and using it like so
void someMethod(PersonRepository people) {
Collection<Person> aggregates =
people.findByLastname("Matthews", Person.class);
Collection<NamesOnly> aggregates =
people.findByLastname("Matthews", NamesOnly.class);
}
I guess the easy way may be is using QueryDSL, that comes with the Spring-Data.
Using to your question the answer can be
JPAQuery query = new JPAQuery(entityManager);
List<Tuple> result = query.from(projects).list(project.projectId, project.projectName);
for (Tuple row : result) {
System.out.println("project ID " + row.get(project.projectId));
System.out.println("project Name " + row.get(project.projectName));
}}
The entity manager can be Autowired and you always will work with object and clases without use *QL language.
As you can see in the link the last choice seems, almost for me, more elegant, that is, using DTO for store the result. Apply to your example that will be:
JPAQuery query = new JPAQuery(entityManager);
QProject project = QProject.project;
List<ProjectDTO> dtos = query.from(project).list(new QProjectDTO(project.projectId, project.projectName));
Defining ProjectDTO as:
class ProjectDTO {
private long id;
private String name;
#QueryProjection
public ProjectDTO(long projectId, String projectName){
this.id = projectId;
this.name = projectName;
}
public String getProjectId(){ ... }
public String getProjectName(){....}
}
Using Spring Data JPA there is a provision to select specific columns from database
---- In DAOImpl ----
#Override
#Transactional
public List<Employee> getAllEmployee() throws Exception {
LOGGER.info("Inside getAllEmployee");
List<Employee> empList = empRepo.getNameAndCityOnly();
return empList;
}
---- In Repo ----
public interface EmployeeRepository extends CrudRepository<Employee,Integer> {
#Query("select e.name, e.city from Employee e" )
List<Employee> getNameAndCityOnly();
}
It worked 100% in my case.
Thanks.
You can use JPQL:
TypedQuery <Object[]> query = em.createQuery(
"SELECT p.projectId, p.projectName FROM projects AS p", Object[].class);
List<Object[]> results = query.getResultList();
or you can use native sql query.
Query query = em.createNativeQuery("sql statement");
List<Object[]> results = query.getResultList();
You can apply the below code in your repository interface class.
entityname means your database table name like projects.
And List means Project is Entity class in your Projects.
#Query(value="select p from #{#entityName} p where p.id=:projectId and p.projectName=:projectName")
List<Project> findAll(#Param("projectId") int projectId, #Param("projectName") String projectName);
It is possible to specify null as field value in native sql.
#Query(value = "select p.id, p.uid, p.title, null as documentation, p.ptype " +
" from projects p " +
"where p.uid = (:uid)" +
" and p.ptype = 'P'", nativeQuery = true)
Project findInfoByUid(#Param("uid") String uid);
You can use the answer suggested by #jombie, and:
place the interface in a separate file, outside the entity class;
use native query or not (the choice depended on your needs);
don't override findAll() method for this purpose but use name of your choice;
remember to return a List parametrized with your new interface (e.g. List<SmallProject>).
Using Native Query:
Query query = entityManager.createNativeQuery("SELECT projectId, projectName FROM projects");
List result = query.getResultList();
public static final String FIND_PROJECTS = "select ac_year_id,ac_year from tbl_au_academic_year where ac_year_id=?1";
#Query(value = FIND_PROJECTS, nativeQuery = true)
public List<Object[]> findByAcYearId(Integer ac_year_id);
this works for me
You can update your JPARepository as below.
#Query("select u.status from UserLogin u where u.userId = ?1 or u.email = ?1 or u.mobile = ?1")
public UserStatus findByUserIdOrEmailOrMobile(String loginId);
Where UserStatus is a Enum
public enum UserStatus
{
New,
Active,
Deactived,
Suspended,
Locked
}
Use:
#Query("SELECT e FROM #{#entityName} e where e.userId=:uid")
List<ClienteEnderecoEntity> findInfoByUid(#Param("uid") UUID uid);
{
"Comments":"Why not using JDBCTemplate",
"Url":"https://www.baeldung.com/spring-jdbc-jdbctemplate"
}

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);

Get method on Rest API e.x search by name or genre?(Spring)

I am trying to build up a REST API for an Artist Project.
So the Project has two tables:
Artist -id,name,genre
Genre-id,name.
Artists class has this object:
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "fk_genre_id")
private Genres genres;
The problem is i have to do a get method that searches Artist by name or genre.
This is what i've tried to do :
#Repository
public interface ArtistRepository extends JpaRepository{
#Query( "Select a FROM Artists a INNER JOIN Genres g on a.genres.getid()=g.getid() where a.name = searchkey or g.name=searchkey")
Artists getArtistbynameorgenre(#Param("name") String searchkey);
}
I don't have perfect knowledge of SQL syntax and i think the problem is at the query syntax.
Instead you can simply use spring data to get the data as follows
#Repository public interface ArtistRepository extends JpaRepository{
List<Artist> artists = findAllByIdOrGenre_Name(String searchkey, String searchkey);
}
As you guess the problem is with your SQL query.
change your query to:
#Query( "Select a FROM Artists a INNER JOIN a.genres g on a.genre = g.genre-id where a.name = ?1 or g.name=?1")
Artists getArtistbynameorgenre(String searchkey);
I hope it works :)

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.

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