sql Query builder in JPA query? - java

I've made a search tool in java.
String query = "SELECT * FROM Customer WHERE 1 = 1 ";
if (!firstname.isEmpty()) query += "AND cName = '" + firstname + "' ";
if (!lastname.isEmpty()) query += "AND cLastName = '" + lastname + "' ";
if (!epost.isEmpty()) query += "AND cEpost = '" + epost + "' ";
if (!phonenumber.isEmpty()) query += "AND cPhonenumber '" + phonenumber + "' ";
That ouput this if all of those paramerets has values:
SELECT * FROM Customer WHERE 1 = 1
AND cName = 'test'
AND cLastName = 'test1'
AND cEpost = 'test2'
AND cPhonenumber 'test3'
This way I can get better results by filling in more data, but i can still choose to not do.. I need a solution for JPA for this.. any tips?
Thanks!
EDIT: End result based on the answer below:
public static List<Customer> searchCustomersByParameters(String firstname, String lastname,
String epost, String phonenumber) {
String sql = "SELECT c FROM Customer c WHERE 1 = 1 ";
if (!firstname.isEmpty()) sql += "AND c.cName = :firstname ";
if (!lastname.isEmpty()) sql += "AND c.cLastName = :lastname ";
if (!epost.isEmpty()) sql += "AND c.cEpost = :epost ";
if (!phonenumber.isEmpty()) sql += "AND c.cPhonenumber = :phonenumber";
Query q = em.createQuery(sql);
if (!firstname.isEmpty()) q.setParameter("firstname", firstname);
if (!lastname.isEmpty()) q.setParameter("lastname", lastname);
if (!epost.isEmpty()) q.setParameter("epost", epost);
if (!phonenumber.isEmpty()) q.setParameter("phonenumber", phonenumber);
return q.getResultList();
}

While it is of course possible to create dynamic SQL using string concatenation as suggested in this answer, a more type safe and less risky (in terms of SQL injection) approach is to use the JPA criteria API
public static List<Customer> searchCustomersByParameters(String firstname, String lastname,
String epost, String phonenumber) {
var qb = em.getCriteriaBuilder();
var query = qb.createQuery(Customer.class);
var root = query.from(Customer.class);
query.select(root);
if (!firstname.isEmpty()) query.where(qb.equal(root.get("cName"), firstName));
if (!lastname.isEmpty()) query.where(qb.equal(root.get("cLastName"), lastname));
if (!epost.isEmpty()) query.where(qb.equal(root.get("cEpost "), epost ));
if (!phonenumber.isEmpty()) query.where(qb.equal(root.get("cPhonenumber "), phonenumber));
return em.createQuery(query).getResultList();
}
... or if you don't strictly need to use JPQL you could also use a third party SQL builder like jOOQ:
public static List<Customer> searchCustomersByParameters(String firstname, String lastname,
String epost, String phonenumber) {
return
ctx.selectFrom(CUSTOMER)
.where(!firstname.isEmpty() ? CUSTOMER.CNAME.eq(firstname) : noCondition())
.and(!lastname.isEmpty() ? CUSTOMER.CLASTNAME.eq(lastname) : noCondition())
.and(!epost.isEmpty() ? CUSTOMER.CEPOST.eq(epost) : noCondition())
.and(!phonenumber.isEmpty() ? CUSTOMER.CPHONENUMBER.eq(phonenumber) : noCondition())
.fetchInto(Customer.class);
}
Disclaimer: I work for the company behind jOOQ

use ? and set Parameters for preventing sql injection and in JPA you can use native sql as old way you do and also JPQL.Generate your sql by conditions and set your parameters.I use here where 1=1 condition to easy append next conditions by and.Otherwise you will have difficulties for appending "where" to your sql.
by native:
public static List<YourEntity> getFromTable(String name,String surname) {
EntityManager em = PersistenceManager.instance().createEntityManager();
try {
String sql = " select * from table where 1=1 ";
if(name!=null && !name.trim().isEmpty()){
sql +=" and name = :name";
}
if(surname!=null && !surname.trim().isEmpty()){
sql +=" and surname = :surname";
}
Query q = em.createNativeQuery(sql);
if(name!=null && !name.trim().isEmpty()){
q.setParameter("name", name);
}
if(surname!=null && !surname.trim().isEmpty()){
q.setParameter("surname", surname);
}
List<YourEntity> l = q.getResultList();
return l;
} finally {
em.close();
}
}
By jpql:
public static List<YourEntity> getFromTable(String name,String surname) {
EntityManager em = PersistenceManager.instance().createEntityManager();
try {
String sql = " select e from YourEntity e where 1=1 ";
if(name!=null && !name.trim().isEmpty()){
sql +=" and e.name = :name";
}
if(surname!=null && !surname.trim().isEmpty()){
sql +=" and e.surname = :surname";
}
Query q = em.createQuery(sql);
if(name!=null && !name.trim().isEmpty()){
q.setParameter("name", name);
}
if(surname!=null && !surname.trim().isEmpty()){
q.setParameter("surname", surname);
}
List<YourEntity> l = q.getResultList();
return l;
} finally {
em.close();
}
}

Related

replace java loop with query in postgresql

I used postgresql 10 and spring boot
I try with success to load tree using loop in java.
in the loop I call each time the query
but it consumes cpu in application server and cpu in database server and takes time to load the tree which contain 5000 unit.
I want using only one query to load the tree without loop in java.
the result from java is ResponseEntity<List<UnitDTO>>
this is my code :
#GetMapping("/unitsBook")
#Timed
public ResponseEntity<List<UnitDTO>> getAllUnitsBook(Pageable pageable, #RequestParam(name="lang", required=false) String lang,
#RequestParam(name="emp", required=false) String empID) {
log.debug("REST request to get a page of Units");
Page<UnitDTO> page = unitService.findAllUnitBook(pageable, lang,empID);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/unitsBook");
return ResponseEntity.ok().headers(headers).body(page.getContent());
}
the java code which contains the loop is :
public List<UnitDTO> getUnitBookList(Pageable pageable, String lang,String empID) {
List<UnitDTO> list=unitRepository.findUnitList(pageable, lang,empID);
List<UnitDTO> unitChildList=getChildrenUnitList(list,lang,pageable,empID);
return unitChildList;
}
private List<UnitDTO> getChildrenUnitList(
List<UnitDTO> unitList, String lang,Pageable pageable,String empID) {
for(UnitDTO UnitDTO : unitList) {
List<UnitDTO> childrenListEntity = unitRepository.findUnitByParentId(pageable, lang,UnitDTO.getValue(),empID);
UnitDTO.setChildren(getChildrenUnitList(childrenListEntity,lang,pageable,empID));
}
return unitList;
}
and the code which call query is :
public List<UnitDTO> findUnitList(Pageable pageable, String lang,String empID) {
String querystr = "SELECT ";
querystr += " unl.name AS text ,";
querystr += " un.id AS value ,";
querystr += " ,cast( 0 as varchar(10) ) as favoriteNbr,cast( null as varchar(10) ) as favoriteId ";
querystr += " FROM public.unit un ";
querystr += " LEFT OUTER JOIN public.unitlang unl ON unl.unit_id = un.id ";
querystr += " Where unl.lang = :lang parentid is null order by app_order asc";
log.debug("-- Query:" + querystr);
Query query = em.createNativeQuery(querystr, "UnitDTOMap");
query.setParameter("lang", lang);
List<UnitDTO> unitDTOs = query.getResultList();
if (pageable.isUnpaged()) {
return unitDTOs;
}
return unitDTOs;
}
#Override
public List<UnitDTO> findUnitByParentId(Pageable pageable, String lang, String idParent,String empID) {
log.debug("-- pageable:" + pageable.getPageNumber() + ", Size:" + pageable.getPageSize() + ", isUnpaged:" + pageable.isUnpaged() + ", lang:" + lang);
lang = lang.toUpperCase();
String querystr = "SELECT ";
querystr += " unl.name AS text ,";
querystr += " un.id AS value ,";
querystr += " (case when cast((select count(*) from employee where employee.unit_id = un.id) as varchar(10)) != '0' then cast(1 as Boolean) else cast(0 as BOOLEAN) end) as disabled";
querystr += " ,cast( 0 as varchar(10) ) as favoriteNbr,cast( null as varchar(10) ) as favoriteId ";
querystr += " FROM unit un ";
querystr += " LEFT OUTER JOIN unitlang unl ON unl.unit_id = un.id ";
querystr += " Where unl.lang = :lang and un.parentid = :idParent order by app_order asc ";
log.debug("-- Query:" + querystr);
Query query = em.createNativeQuery(querystr, "UnitBookDTOMap");
query.setParameter("lang", lang);
query.setParameter("idParent", idParent);
List<UnitDTO> unitDTOs = query.getResultList();
log.debug("-- unitDTOs Size:" + unitDTOs.size());
if (pageable.isUnpaged()) {
return unitDTOs;
}
return unitDTOs;
}
Updated :
I try to use the recursive query but the problem is that the tree does not display correctly.
all the unit are in same level.
I think the problem in this line WHERE id = :idParent I comment it because I didn't find how can I send it
public List<UnitDTO> getUnitBookList(Pageable pageable, String lang) {
List<UnitDTO> list=unitRepository.findUnitList(pageable, lang);
// List<UnitBookDTO> unitChildList=getChildrenUnitList(list,lang,pageable);
return list;
}
#Override
public List<UnitDTO> findUnitList(Pageable pageable, String lang) {
log.debug("-- pageable:" + pageable.getPageNumber() + ", Size:" + pageable.getPageSize() + ", isUnpaged:" + pageable.isUnpaged() + ", lang:" + lang);
lang = lang.toUpperCase();
String querystr = "WITH RECURSIVE un_id AS ( ";
querystr += " SELECT id";
querystr += " FROM unit ";
// querystr += " WHERE id = :idParent";
querystr += " UNION";
querystr += " SELECT unit.id";
querystr += " FROM unit JOIN un_id ON unit.parentid = un_id.id ) ";
querystr += " SELECT unl.name AS text, un.id AS value, (case when cast((select count(*) from employee where employee.unit_id = un.id) as varchar(10)) != '0' ";
querystr += " then cast(1 as Boolean) else cast(0 as BOOLEAN) end) as disabled , cast(0 as varchar(10)) as favoriteNbr,";
querystr += " cast(null as varchar(10)) as favoriteId FROM un_id JOIN unit un USING (id) LEFT OUTER JOIN unitlang unl ON unl.unit_id = un.id Where unl.lang = :lang order by app_order asc";
log.debug("-- Query:" + querystr);
Query query = em.createNativeQuery(querystr, "UnitDTOMap");
query.setParameter("lang", lang);
List<UnitDTO> unitDTOs = query.getResultList();
log.debug("-- unitDTOs Size:" + unitDTOs.size());
if (pageable.isUnpaged()) {
return unitDTOs;
}
return unitDTOs;
}
The usual way to fetch a tree in SQL is to use a recursive Common Table Expression (CTE):
WITH RECURSIVE un_id AS (
SELECT id
FROM unit
WHERE id = :idParent -- or parentid if you want to exclude the parent itself
UNION
SELECT unit.id
FROM unit
JOIN un_id
ON unit.parentid = un_id.id
)
SELECT unl.name AS text,
un.id AS value,
(case
when cast((select count(*) from employee where employee.unit_id = un.id) as varchar(10)) != '0'
then cast(1 as Boolean)
else cast(0 as BOOLEAN) end) as disabled
,
cast(0 as varchar(10)) as favoriteNbr,
cast(null as varchar(10)) as favoriteId
FROM un_id -- reference to the CTE
JOIN unit un
USING (id)
LEFT OUTER JOIN unitlang unl ON unl.unit_id = un.id
Where unl.lang = :lang
order by app_order asc
Recursively fetch all the IDs and then join the rest of the needed data.

sql for paging notice board in jsp

I'm trying to make a notice board and before apply paging function it showed posts well.
but after i applied paging fuction it shows only one post and paging function shows pages well.
so i think this is a sql problem.
when i try to show count it shows 1. even i tried with this shows 1.
sql = "select * from mvc_board ";
this is my sql
String sql = "SELECT ROWNUM, bId, bName, bTitle, bContent," +
"bDate, bHit, bGroup, bStep, bIndent " +
"from(select * from mvc_board order by bGroup DESC , bStep asc)";
i tried to make mine from this sql. but couldn't understand.
sql = "select *, (select u_name from user where idx = writer_fk) writer, (select idx from answer where idx = answer_fk) answer from board order by idx desc limit "+startRow+", "+endRow;
ref. http://queserasera.tistory.com/14
this is DAO
public ArrayList<BDto> getBoardList(int startRow, int endRow, String keyField, String keyWord) {
ArrayList<BDto> dtos = new ArrayList<BDto>();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
String sql = "SELECT ROWNUM, bId, bName, bTitle, bContent," +
"bDate, bHit, bGroup, bStep, bIndent " +
"from(select * from mvc_board order by bGroup DESC , bStep asc)";
System.out.println(sql);
try{
if(keyWord != null && !keyWord.equals("") && !keyWord.equals("null")) {
sql += " WHERE " + keyField.trim() +" LIKE '%"+keyWord.trim()+"%'";
}
connection = dataSource.getConnection();
//특정행부터 레코드를 가져오기 위해서 옵션 설정
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
int count=0;
int ROWNUM = resultSet.getInt("ROWNUM");
int bId = resultSet.getInt("bId");
String bName = resultSet.getString("bName");
String bTitle = resultSet.getString("bTitle");
String bContent = resultSet.getString("bContent");
Timestamp bDate = resultSet.getTimestamp("bDate");
int bHit = resultSet.getInt("bHit");
int bGroup = resultSet.getInt("bGroup");
int bStep = resultSet.getInt("bStep");
int bIndent = resultSet.getInt("bIndent");
BDto dto = new BDto(ROWNUM, bId, bName, bTitle, bContent, bDate, bHit, bGroup, bStep, bIndent);
dtos.add(dto);
count++;
System.out.println(count);
//while문끝
}//if문끝
}catch (Exception e) {
System.out.println(e+"=> getBoardList fail");
}finally {
try {
if(resultSet != null) resultSet.close();
if(preparedStatement != null) preparedStatement.close();
if(connection != null) connection.close();
} catch (Exception e2) {
// TODO: handle exception
e2.printStackTrace();
}
}
return dtos;
}
=============added sql on sql developer==================================
select rn, bId, bName, bTitle, bContent, bDate, bHit, bGroup, bStep, bIndent
from ( select b.*, row_number() over (order by bGroup DESC, bStep asc) rn
from mvc_board b %s )
where rn between 1 and 10
order by rn;
The main problem in your getBoardList() method was the usage of if (resultSet.next()) instead of while (resultSet.next()) when iterating through the result set; that caused the code to only ever return a single DTO.
There are other things you should also consider. For example, starting from Java 7, it's preferrable to use the try-with resources statement instead of traditional try-finally for JDBC resource handling.
An important thing to note is that injecting field names like keyField from the UI is a really bad practice because it makes the query vulnerable to SQL injection.
You should at least create a static whitelist of allowed field names to sanitize the input, if you cannot use static WHERE conditions.
Finally, paging queries in Oracle 11g are traditionally done with an inline view that uses the row_number analytic function. Note that row-numbering starts from 1. Starting from Oracle 12c, the row-limiting clause can be used and the inline view is no longer needed.
An example implementation that considers the above points could look like the following:
public List<BDto> getBoardList(int startRow, int endRow, String keyField, String keyWord) {
List<BDto> dtos = new ArrayList<BDto>();
// the %s in the template will be replaced with a
// WHERE condition when a keyword is present
final String sqlTemplate = "select rn, bId, bName, bTitle, "
+ "bContent, bDate, bHit, bGroup, bStep, bIndent "
+ "from ( "
+ " select b.*, row_number() over (order by bGroup DESC, bStep asc) rn"
+ " from mvc_board b %s "
+ " ) "
+ " where rn between ? and ? "
+ "order by rn";
boolean whereCondition = false;
String sql = null;
if (keyWord != null && !keyWord.equals("") && !keyWord.equals("null")) {
sql = String.format(sqlTemplate,
" WHERE " + keyField.trim() + " LIKE '%' || ? || '%'");
whereCondition = true;
} else {
sql = String.format(sqlTemplate, "");
}
System.out.println(sql);
try (Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
int parameterIndex = 1;
if (whereCondition) {
preparedStatement.setString(parameterIndex++, keyWord);
}
preparedStatement.setInt(parameterIndex++, startRow);
preparedStatement.setInt(parameterIndex, endRow);
try (ResultSet resultSet = preparedStatement.executeQuery()) {
int count = 0;
while (resultSet.next()) {
int ROWNUM = resultSet.getInt("rn");
int bId = resultSet.getInt("bId");
String bName = resultSet.getString("bName");
String bTitle = resultSet.getString("bTitle");
String bContent = resultSet.getString("bContent");
Timestamp bDate = resultSet.getTimestamp("bDate");
int bHit = resultSet.getInt("bHit");
int bGroup = resultSet.getInt("bGroup");
int bStep = resultSet.getInt("bStep");
int bIndent = resultSet.getInt("bIndent");
dtos.add(new BDto(ROWNUM, bId, bName, bTitle,
bContent, bDate, bHit, bGroup, bStep, bIndent));
count++;
}
System.out.println(count);
}
} catch (SQLException e) {
System.out.println(e + "=> getBoardList fail");
}
return dtos;
}
In the example you gave us the paging is added by the limit clause, but ORACLEhas it's own syntax for these. Try:
String sql = "SELECT rown, bId, bName, bTitle, bContent," +
"bDate, bHit, bGroup, bStep, bIndent " +
"from(select rownum rown, mvc_board.* from mvc_board order by bGroup DESC , bStep asc) " +
"WHERE rown between ? and ? ";
Bind startRow and endRow before executing the query:
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,startRow);
preparedStatement.setInt(2,endRow);
resultSet = preparedStatement.executeQuery();
Change the WHERE to AND:
sql +=" and " + keyField.trim()+" LIKE '%"+keyWord.trim()+"%'" ;
And finally change the if(resultSet.next()){ back to a while loop to get more then one row as result.

JSP - How to write a valid search form

I have a problem with a search form in a JSP project. In particular an user can subscribe to an event and the admin must be able to search all the members of that event.
I write a form like this:
out.println("<fieldset><legend>Search</legend><form action=\"cercaIscritti.jsp\" method=\"post\">"
+ "Name: <input name=\"name\" type=\"text\"><br>Surname: <input name=\"surname\" type=\"text\"><br>"
+ "Belonging: <input name=\"belonging\" type=\"text\"><br>Countr: <input name=\"country\" type=\"text\"><br>"
+ "<input type=\"submit\" value=\"Cerca\"></fieldset></form>");
(If it isn't readable: there are four <input>, one for each column in the table user and a <input> for submit).
I would like all the fields to be optional so that the admin can fill what it wants, but how do I build the query?
I tried something like this :
public ResultSet searchSubs(String name, String surname, String belonging, String country) {
try {
boolean n = false, s = false, b = false, c = false;
String query = "SELECT * FROM user WHERE ";
if (!isEmpty(name)) {
query += "firstName = ?";
n = true;
}
if (!isEmpty(surname)) {
if (n) {
query += " AND lastName = ?";
} else {
query += "lastName = ?";
}
s = true;
}
if (!isEmpty(belonging)) {
if (n || s) {
query += " AND belonging = ?";
} else {
query += "belonging = ?";
}
b = true;
}//and go on
But how can I add the values with the PreparedStatement? Is this the correct way? If it's not, how can I do something like that?
The booleans are there in the middle only for test, I thought I would use them in some way but I do not know how.
Here is a way you can follow to search with multiples values :
public ResultSet searchSubs(String name, String surname, String belonging, String country){
try {
String query = "SELECT * FROM user WHERE 1=1";
//---------------------------------------^^^
int index = 1;
if (!name.isEmpty()) {
query += " AND firstName = ?";
}
if (!surname.isEmpty()) {
query += " AND surname = ?";
}
if (!belonging.isEmpty()) {
query += " AND belonging = ?";
}
if (!country.isEmpty()) {
query += " AND country = ?";
}
PreparedStatement ps = connection.prepareStatement(query);
if (!name.isEmpty()) {
ps.setString(index++, name);
}
if (!surname.isEmpty()) {
ps.setString(index++, surname);
}
if (!belonging.isEmpty()) {
ps.setString(index++, belonging);
}
if (!country.isEmpty()) {
ps.setString(index++, country);
}
ResultSet rs = ps.executeQuery();
//...
The idea is simple :
In the Where clause use 1=1 to not get an error if the user not enter any value(in this case your query is SELECT * FROM user WHERE 1=1 and it will return every thing)
The first part if to fill the query with the non empty fields
The second if to fill the prepared statement with the right values.
finally execute the prepared statement and get the results.

Hibernate Query whit function count

I am studying Hibernate and in a exercise I must do this query
"SELECT count(id) as numero_utenti, imc
FROM Utenti
WHERE azienda = '" + id + "' GROUP BY imc"
the problem is that, I don't know as view the result and to save the result in a string.
Thanks.
This is the function
public String getStat(int id) {
String stat = "";
int count = 0;
try {
Query query = session.createQuery("SELECT count(id) as numero_utenti, imc FROM Utenti WHERE azienda = '" + id + "' GROUP BY imc");
// as I extract the values​​?
tx.commit();
} catch (HibernateException he) {
throw he;
}
return stat;
}
If you are looking how to execute query using hibernate session.
query = session.createQuery("semect * from temp");
and query instance
Long.valueOf(query.fetchCountOfRows()).intValue();
It'll give you no. row count.
If they are not unique, get the list.
List<String> imcs= null;
int count = 0;
Query query = session.createQuery("SELECT imc FROM Utenti WHERE azienda = '" + id + "' GROUP BY imc");
imcs = query.list();
count = imcs.size();

Simple query with variable parameters

I have three queries which I would like to consolidate into one query which can accept a variable length of WHERE arguments; I cannot remember for the life of me how to do this.
PreparedStatement queryOne = connection.prepareStatement
("SELECT columnOne, columnTwo, columnThree FROM tableOne WHERE columnOne = ?;" );
PreparedStatement queryTwo = connection.prepareStatement
("SELECT columnOne, columnTwo, columnThree FROM tableOne WHERE columnTwo = ?;" );
PreparedStatement queryThree = connection.prepareStatement
("SELECT columnOne, columnTwo, columnThree FROM tableOne WHERE columnOne = ? AND columnTwo = ?;" );
All three queries select the same columns from the same table, so their union can be easily done in one statement:
SELECT columnOne, columnTwo, columnThree
FROM tableOne
WHERE columnOne = ?
or columnTwo = ?
or (columnOne = ? AND columnTwo = ?)
Answered my own question, feel free to chime in on whether this is bad practice or not.
String columnOne = getValue();
String columnTwo = getValue();
String queryString = "SELECT columnOne, columnTwo, columnThree FROM tableOne"
if (columnOne != null && columnTwo != null)
queryString = queryString + "WHERE columnOne = ? AND columnTwo = ?"
else if (columnOne != null)
queryString = queryString + "WHERE columnOne = ?"
else if (columnTwo != null)
queryString = queryString + "WHERE columnTwo = ?"
PreparedStatement query = connection.prepareStatement(queryString);

Categories

Resources