I am new to golang development. I have 6 parameters to pass into query using gorm. this is the select query so, based on input value we need to do filter the values. So, we need to pass the filters dynamically into query. I tried but no solution.
func GetUsers(DB *gorm.DB, Offset int, Limit int, User uibackendmodels.UserDetails) (Users []uibackendmodels.UserDetails, Err error) {
query := "SELECT userid, username, nickname, email, mobile, location, status, roleids, trsids, brandids, languagecode, createdat, createdby, modified, modifiedby" +
" FROM users WHERE 1=1 "
if User.UserName != "" || User.NickName != "" {
nameQuery := "("
if User.UserName != "" {
nameQuery = nameQuery + " username LIKE " + "'" + User.UserName + "%'"
}
if User.NickName != "" && User.UserName != "" {
nameQuery = nameQuery + " OR nickname LIKE " + "'" + User.NickName + "%'"
} else if User.NickName != "" {
nameQuery = nameQuery + " nickname LIKE " + "'" + User.NickName + "%'"
}
query = query + " AND " + nameQuery + ")"
}
if User.BrandIDs != nil && len(User.BrandIDs) > 0 {
brandIds := "("
for i := range User.BrandIDs {
if len(User.BrandIDs) == (i + 1) {
brandIds = brandIds + "'" + User.BrandIDs[i] + "' = ANY (brandids) "
} else {
brandIds = brandIds + "'" + User.BrandIDs[i] + "' = ANY (brandids) OR "
}
}
query = query + " AND " + brandIds + ")"
}
if User.TRSIDs != nil && len(User.TRSIDs) > 0 {
trsIds := "("
for i := range User.TRSIDs {
if len(User.TRSIDs) == (i + 1) {
trsIds = trsIds + "'" + User.TRSIDs[i] + "' = ANY (trsids) "
} else {
trsIds = trsIds + "'" + User.TRSIDs[i] + "' = ANY (trsids) OR "
}
}
query = query + " AND " + trsIds + ")"
}
if User.RoleIDs != nil && len(User.RoleIDs) > 0 {
roleIds := "("
for i := range User.RoleIDs {
if len(User.RoleIDs) == (i + 1) {
roleIds = roleIds + strconv.FormatInt(int64(User.RoleIDs[i]), 10) + " = ANY (roleids) "
} else {
roleIds = roleIds + strconv.FormatInt(int64(User.RoleIDs[i]), 10) + " = ANY (roleids) OR "
}
}
query = query + " AND " + roleIds + ")"
}
if User.Status != "" {
query = query + " AND status = " + "'" + EnumToString(User.Status) + "'"
}
query = query + " AND status != 'deleted' ORDER BY modified desc LIMIT " + strconv.Itoa(Limit) + " OFFSET " + strconv.Itoa(Offset)
if err := DB.Raw(query).Scan(&Users).Error; err != nil {
return nil, err
}
return Users, nil
}
I need to add parameters dynamically to above query.
In GORM, you could handle the basic inputs by enriching the Object and passing it to the function, similar to this:
// Would get the First user with Name="jinzhu" and Age="20"
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
Whenever you cannot express a part of your statement using GORMs default functions, you can use normal SQL and Method Chaining:
// Would get the first User with Name="jinzhu" and id in (4,5,6,7)
ids := [4]int{4,5,6,7}
db.Where(&User{Name: "jinzhu"}).Where("id IN (?)", ids).First(&user)
You could also chain methods using variables
base := db.Where(&User{Name: "jinzhu"})
if someThing {
base = base.Where("id IN (?)", ids) // Adding this condition just if someThing is true
}
base.First(&user) // Query your results
More Information about Method Chaining
Related
In my SpringBoot project I have CrudRepository with next method:
public interface LoanRequestRepository extends CrudRepository<LoanRequest, UUID> {
#Query(
value = "select " +
"lr.id as id," +
"lr.state as state," +
"lr.iban as iban," +
"lr.amount as amount," +
"lr.rate as rate," +
"lr.term as term," +
"lr.best_offer as best_offer," +
"lr.contract_number as contract_number," +
"lr.created as created," +
"lr.company_id as company_id," +
"lr.closed as closed," +
"lr.loan_colvir_id as loan_colvir_id," +
"lr.contract_date as contract_date," +
"lr.first_payment_date as first_payment_date, " +
"lr.gesv as gesv, " +
"c.id as comp_id," +
"c.name as comp_name," +
"c.bin as comp_bin," +
"c.colvir_id as comp_colvir_id," +
"c.kod as comp_kod," +
"c.type as comp_type," +
"c.location_address as comp_location_address," +
"c.registration_address as comp_registration_address," +
"c.legal_address as comp_legal_address," +
"c.actual_address as comp_actual_address," +
"c.department_code as comp_department_code," +
"c.department_name as comp_department_name," +
"c.resident as comp_resident," +
"c.created as comp_created," +
"c.registration_date as comp_registration_date," +
"c.economic_sector as comp_economic_sector," +
"c.short_name as comp_short_name," +
"c.colvir_reference_id as comp_colvir_reference_id," +
"c.card_system_id as comp_card_system_id," +
"c.initial_registration as comp_initial_registration " +
"from loan_request lr " +
"left join company c on c.id = lr.company_id " +
"where 1=1 " +
"and case when :amount is not null " +
" then lr.amount = :amount " +
" else 1=1 end " +
"and case when cast(:dateFrom as date) is not null " +
" then lr.created >= cast(:dateFrom as date)" +
" else 1=1 end " +
"and case when cast(:dateTo as date) is not null " +
" then lr.created <= cast(:dateTo as date) " +
" else 1=1 end " +
"and case when :bin is not null " +
" then c.bin like concat('%', :bin, '%') " +
" else 1=1 end " +
"and case when :companyName is not null " +
" then lower(c.name) like lower(concat('%', :companyName, '%')) " +
" else 1=1 end " +
" group by lr.id, c.id " +
"order by " +
" CASE WHEN :isAscending = true THEN :orderBy END ASC, " +
" CASE WHEN :isAscending = false THEN :orderBy END DESC " +
"limit :size offset :page;")
Iterable<LoanRequestDto> findAllLoanRequestDtoByFilters(#Param("dateFrom") LocalDate dateFrom,
#Param("dateTo") LocalDate dateTo,
#Param("bin") String bin,
#Param("amount") BigDecimal amount,
#Param("companyName") String companyName,
#Param("page") Integer page,
#Param("size") Integer size,
#Param("orderBy") String orderBy,
#Param("isAscending") Boolean isAscending);
}
All query works well, but it ignores "order by" with case expression and list comes in inordered way. I am sending values as "lr.iban", "c.name" to orderBy parameter, and if translate it to sql, it works. But in JPQL it does not work.
So, where am I wrong? How can I solve it?
Also, if change last part with CASE expression to the next:
order by CASE WHEN :isAscending = true THEN c.name END ASC
It also works, well in JPQL. List comes in ordered way.
Let's say I've got below java code which eventually prints everything from the pl/sql query:
try (CallableStatement call = c.prepareCall(
"declare "
+ " num integer := 1000;"
+ " num myString:= 'test';"
+ "begin "
+ " dbms_output.enable();"
+ " dbms_output.put_line('abc');"
+ " dbms_output.put_line('hello');"
+ " dbms_output.put_line(myString);"
+ " dbms_output.get_lines(?, num);"
+ " dbms_output.disable();"
+ "end;"
)) {
call.registerOutParameter(1, Types.ARRAY, "DBMSOUTPUT_LINESARRAY");
call.execute();
Array array = null;
try {
array = call.getArray(1);
System.out.println(Arrays.asList((Object[]) array.getArray()));
}
finally {
if (array != null)
array.free();
}
}
That works fine but what if I wanted to pass a parameter to the pl/sql query? For example 'myString'?
So it would be something like:
"declare "
+ " num integer := 1000;"
+ " num myString:= ?;"
+ "begin "
+ " dbms_output.enable();"
+ " dbms_output.put_line('abc');"
+ " dbms_output.put_line('hello');"
+ " dbms_output.put_line(myString);"
+ " dbms_output.get_lines(?, num);"
+ " dbms_output.disable();"
+ "end;"
How should I pass this parameter? I know that I should use sth like
call.setString(2, "test");
but in what place/line?
Can you please help?
In this case, your input parameter will have index=1 and dbms_output out parameter will have index=2;
There is no type myString, so looks like you want varchar2;
You can use .setString();
I've fixed your variable names:
try (CallableStatement call = c.prepareCall(
"declare "
+ " num integer := 1000;"
+ " str varchar2(100):= ?;"
+ "begin "
+ " dbms_output.enable();"
+ " dbms_output.put_line('abc');"
+ " dbms_output.put_line('hello');"
+ " dbms_output.put_line(str);"
+ " dbms_output.get_lines(?, num);"
+ " dbms_output.disable();"
+ "end;"
)) {
call.setString(1, "test");
call.registerOutParameter(2, Types.ARRAY, "DBMSOUTPUT_LINESARRAY");
call.execute();
Array array = null;
try {
array = call.getArray(2);
System.out.println(Arrays.asList((Object[]) array.getArray()));
}
finally {
if (array != null)
array.free();
}
}
Results:
Connected successfully
[abc, hello, test, null]
Another variant is to bind variables by name:
try (CallableStatement call = c.prepareCall(
"declare "
+ " num integer := 1000;"
+ " str varchar2(100):= :in;"
+ "begin "
+ " dbms_output.enable();"
+ " dbms_output.put_line('abc');"
+ " dbms_output.put_line('hello');"
+ " dbms_output.put_line(str);"
+ " dbms_output.get_lines(:out, num);"
+ " dbms_output.disable();"
+ "end;"
)) {
call.setString("in", "test");
call.registerOutParameter("out", Types.ARRAY, "DBMSOUTPUT_LINESARRAY");
call.execute();
Array array = null;
try {
array = call.getArray("out");
System.out.println(Arrays.asList((Object[]) array.getArray()));
}
finally {
if (array != null)
array.free();
}
}
As you can see I used :in and :out as bind variables and used these names in .setString, registerOutParameter and getArray.
I have got a webapp(JSP/Servlet) with Tomcat8 + SQL Server2012
JDBC Driver Type 4: JTDS old version 1.2.5 (http://jtds.sourceforge.net/)
I change this kind of query, adding Prepared Statement (server pagining)
Sting DDXsql = "SELECT '?' *, ( DDX_RECORD_COUNT / '?' + 1 ) AS DDX_PAGE_COUNT
FROM
( SELECT '?' *
FROM ( SELECT '?' *,
(SELECT COUNT(*) " + "FROM "
+ session.getAttribute("DatabaseName") + ".G1_grid "
+ sqlFrom
+ sqlWhere + " "
+ " ) AS DDX_RECORD_COUNT "
+ "FROM " + session.getAttribute("DatabaseName") + ".G1_grid "
+ sqlFrom
+ sqlWhere + " "
+ " ORDER BY '?' '?' , '?' '?' ) AS TMP1 ORDER
BY '?' '?', '?' '?') AS r ORDER BY '?' '?', '?' '?'";
Parameters:
String top1 = DBManager.getTOP(request, "TOP " + Integer.valueOf((String)ResourceManager.findData("pageSize", request)));
Integer pagesizeInt = Integer.valueOf((String)ResourceManager.findData("pageSize", request));
String top2 = DBManager.getTOP(request, "TOP " + Integer.valueOf((String)ResourceManager.findData("ddxrecordcount", request)));
String top3= DBManager.getTOP(request, "TOP " + Integer.valueOf((String)ResourceManager.findData("toRange", request)));
String notSortStr = (String)ResourceManager.findData("notSort", request);
Object[] values = new Object[] {
top1,
pagesizeInt,
top2,
top3,
SortKey,
Sort,
TotalSortKey,
Sort,
SortKey,
notSortStr,
TotalSortKey ,
notSortStr,
SortKey,
Sort,
TotalSortKey,
Sort
};
Before, I didint use PreparedStatement I have this kind of query (replace "?" with the Object array values, without StringEscapeUtils):
String DDXsql = "SELECT " +
DBManager.getTOP(request, "TOP "
+ Integer.valueOf(StringEscapeUtils.escapeSql((String)ResourceManager.findData("pageSize", request)))) + " *,
( DDX_RECORD_COUNT / " + Integer.valueOf(StringEscapeUtils.escapeSql((String)ResourceManager.findData("pageSize", request))) + " + 1 ) AS DDX_PAGE_COUNT FROM
( SELECT "
+ DBManager.getTOP(request, "TOP "
+ Integer.valueOf(StringEscapeUtils.escapeSql((String)ResourceManager.findData("ddxrecordcount", request))))
+ " * FROM ( SELECT " + DBManager.getTOP(request, "TOP " + Integer.valueOf(StringEscapeUtils.escapeSql((String)ResourceManager.findData("toRange", request))))
+ " *, (SELECT COUNT(*) "
+ "FROM " + session.getAttribute("DatabaseName") + ".G1_grid " + sqlFrom + sqlWhere + " " + " ) AS DDX_RECORD_COUNT "
+ "FROM " + session.getAttribute("DatabaseName")
+ ".G1_grid " + sqlFrom + sqlWhere + " " + " ORDER BY "
+ StringEscapeUtils.escapeSql(SortKey) + " " + StringEscapeUtils.escapeSql(Sort) + ", "
+ StringEscapeUtils.escapeSql(TotalSortKey) + " "
+ StringEscapeUtils.escapeSql(Sort) + ") AS TMP1 ORDER BY "
+ StringEscapeUtils.escapeSql(SortKey) + " "
+ StringEscapeUtils.escapeSql((String)ResourceManager.findData("notSort", request))
+ ", " + StringEscapeUtils.escapeSql(TotalSortKey) + " "
+ StringEscapeUtils.escapeSql((String)ResourceManager.findData("notSort", request)) + " ) AS r ORDER BY "
+ StringEscapeUtils.escapeSql(SortKey) + " "
+ StringEscapeUtils.escapeSql(Sort) + ", "
+ StringEscapeUtils.escapeSql(TotalSortKey)
+ " " + StringEscapeUtils.escapeSql(Sort) + " ";
The last query runs without error, System.out of this query give this for example:
SELECT TOP 20 *, ( DDX_RECORD_COUNT / 20 + 1 ) AS DDX_PAGE_COUNT
FROM
( SELECT TOP 20 * FROM
( SELECT TOP 20 *,
(SELECT COUNT(*)
FROM SuiteMA_DIP.dbo.G1_grid
WHERE 1 = 1 ) AS DDX_RECORD_COUNT
FROM SuiteMA_DIP.dbo.G1_grid WHERE 1 = 0 ORDER BY DATA_ISCRIZIONE_ORDER DESC, SOGGETTO_RILEVANTE_PAID DESC) AS TMP1 ORDER BY DATA_ISCRIZIONE_ORDER ASC, SOGGETTO_RILEVANTE_PAID ASC ) AS r ORDER BY DATA_ISCRIZIONE_ORDER DESC, SOGGETTO_RILEVANTE_PAID DESC
But when i run sql with preparedStatement:
java.sql.SQLException: Invalid parameter index 1.
at net.sourceforge.jtds.jdbc.JtdsPreparedStatement.getParameter(JtdsPreparedStatement.java:340)
at net.sourceforge.jtds.jdbc.JtdsPreparedStatement.setParameter(JtdsPreparedStatement.java:409)
at net.sourceforge.jtds.jdbc.JtdsPreparedStatement.setObjectBase(JtdsPreparedStatement.java:395)
at net.sourceforge.jtds.jdbc.JtdsPreparedStatement.setObject(JtdsPreparedStatement.java:667)
at org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement.setObject(DelegatingPreparedStatement.java:188)
at org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement.setObject(DelegatingPreparedStatement.java:188)
at it.netbureau.jfx.db.SQLDBManager.execSQL(SQLDBManager.java:57)
at it.netbureau.jfx.db.SQLDBManager.execSQL(SQLDBManager.java:78)
at org.apache.jsp.G1.select_jsp._jspService(select_jsp.java:691)
The java method execute the query :
class jfx.db.SQLDBManager.execSQL:
public Object execSQL(PreparedStatement stmt, Object values[], String xmlId)
throws SQLException
{
Object result = null;
if(stmt == null)
return null;
try
{
for(int i = 0; i < values.length; i++)
if(values[i] == null)
stmt.setNull(i + 1, 4);
else
stmt.setObject(i + 1, values[i]); <--this give exception!
if(stmt.execute()) result = transform(stmt.getResultSet(), xmlId);
}
catch(SQLException ex)
{
rollback();
throw ex;
}
return result;
}
What's wrong?
Thank you very much
roby
Your query does not contain any parameters, a '?' is just a literal string with a question mark in it, it is not a parameter.
You also can't parameterize object names like column names and clauses (like a TOP 20), so even if you'd change it to - for example - order by ?, ... it wouldn't work, as you'd be sorting by the string value (which would be the same for all rows, so effectively you wouldn't be sorting at all).
To do what you want to do you will need to concatenate the column names (and other clauses) into the query string. This also means that you might open yourself up to SQL injection: be sure to check the values carefully (for example against a whitelist of allowed values).
Today i was doing self-review of my code and code didn't look nice to me (Although it is working as intended). So i want a better way of doing the same.
Following is the case:
In my webservice, client can perform search operation on records by n number of search criteria ( one possible criteria can be get me all the staff whose name contains A and who has designation Teacher. So for such query i have many if statements that is making string query accordingly. but this is looking ugly to me. Is their any way to achieve this using PreparedStatement.
Ugly code looks like below
private String getSearchQuery(Staff staffEntity) {
boolean hasAnySearchParam = false;
String query = null;
StringBuilder querybuilder = new StringBuilder("select * from " + DBConstants.StaffBasicInfo.tableName + " left outer join " + DBConstants.StaffAdvInfo.tableName + " on "
+ DBConstants.StaffBasicInfo.staffId + " = " + DBConstants.StaffAdvInfo.staff_adv_info_staffId + " where");
if (staffEntity.getName() != null && (!staffEntity.getName().isEmpty())) {
querybuilder.append(" " + DBConstants.StaffBasicInfo.staffname + " Like '%" + staffEntity.getName() + "%'");
querybuilder.append(" and");
hasAnySearchParam = true;
}
if (staffEntity.getDesignation() != null && (!staffEntity.getDesignation().isEmpty())) {
querybuilder.append(" " + DBConstants.StaffBasicInfo.designation + " Like '%" + staffEntity.getDesignation() + "%'");
querybuilder.append(" and");
hasAnySearchParam = true;
}
if (staffEntity.getAge() != null) {
querybuilder.append(" " + DBConstants.StaffBasicInfo.age + " = " + staffEntity.getAge());
querybuilder.append(" and");
hasAnySearchParam = true;
}
if (staffEntity.getUsername() != null && (!staffEntity.getUsername().isEmpty())) {
querybuilder.append(" " + DBConstants.StaffAdvInfo.username + " Like '%" + staffEntity.getUsername() + "%'");
querybuilder.append(" and");
hasAnySearchParam = true;
}
if (staffEntity.getRole() != null && (!staffEntity.getRole().isEmpty())) {
querybuilder.append(" " + DBConstants.StaffAdvInfo.role + " Like '%" + staffEntity.getRole() + "%'");
querybuilder.append(" and");
hasAnySearchParam = true;
}
if (false == hasAnySearchParam) {
throw new IllegalArgumentException("Check Json: No parameter to search");
} else {
// need to clean query.
query = querybuilder.substring(0, querybuilder.length() - 3);
}
return query;
}
Note Right now i am more concerned about code clarity, simplicity and ease to use, i can think of performance issues later.
For this particular case I would use static query with params where params are set from java
WHERE
...
and (:userNameParam is null or staffname like concat('%',:staffnameParam,'%'))
and (:userNameParam is null or username like concat('%',:userNameParam ,'%'))
...
Then just pass parameters userNameParam, userNameParam etc. If they are null or empty just pass null.
Your way leaves possibility for SQL injection
Following code needs modification can we use constants files if yes then how we can segregate string in separate files so that loop continues then performance is not impacted
for (int j = 0; j < subconfigListRLC.size(); j++) {
StringBuffer sqlQuery = new StringBuffer();
test = (SubConfigurationDetailsObject) subconfigListRLC.get(j);
if (test.getFlag().equalsIgnoreCase("T")) {
sqlQuery = sqlQuery.append("update SUB_CONFIG set TSTED = "
+ test.getSubConfigurationIndexNo() + " , Q_SUB_INDX = 0 "
+ "where BASE_ENG_KEY = '" + test.getBaseEngineKey() + "' "
+ "AND MODEL_YEAR = '" + modelYear + "' "
+ "AND RLHP_LVW = '" + test.getRoadLoadHorsepowerValue() + "' "
+ "AND LVW_TEST_WT_WO_CONT = '" + test.getEtwValue() + "' "
+ "AND INERTIA_WT_CLASS = '" + test.getInertiaWeightClassNo() + "' "
+ "AND TEST_GROUP_ID = " + test.getTestGroupId() + " "
+ "AND ENGINE_CODE = '" + test.getEngineCode() + "' "
+ "AND AXLE_RATIO = '" + test.getAxleRatioValue() + "'");
} else if (test.getFlag().equalsIgnoreCase("U")) {
sqlQuery = sqlQuery.append("update SUB_CONFIG set Q_SUB_INDX = "
+ test.getSubConfigurationIndexNo() + " "
+ "where BASE_ENG_KEY = '" + test.getBaseEngineKey() + "' "
+ "AND MODEL_YEAR = '" + modelYear + "' "
+ "AND RLHP_LVW = '" + test.getRoadLoadHorsepowerValue() + "' "
+ "AND LVW_TEST_WT_WO_CONT = '" + test.getEtwValue() + "' "
+ "AND INERTIA_WT_CLASS = '" + test.getInertiaWeightClassNo() + "' "
+ "AND TEST_GROUP_ID = " + test.getTestGroupId() + " "
+ "AND ENGINE_CODE = '" + test.getEngineCode() + "' "
+ "AND AXLE_RATIO = '" + test.getAxleRatioValue() + "'");
}
//System.out.println("Query----------->"+sqlQuery.toString());
processor.getUpdateAccessor().executeUpdateSql(sqlQuery.toString());
}
Use a Java PreparedStatement in order to build the query. You can then store the query string for the PreparedStatement in a properties file. Read the two possible query strings into variables before entering the loop (in fact you may want to build both PreparedStatements before entering the loop - depending on whether you always use them both). You can then call clearParamaters, then set your new parameters, execute, repeat.
As you asked for the exact details, something like this. Search for javadocs on PreparedStatement. Javadocs are always worth reading.
String sql = "update SUB_CONFIG set TSTED = ? , Q_SUB_INDX = ? " +
"where BASE_ENG_KEY = ? " +
"AND MODEL_YEAR = ? " +
"AND RLHP_LVW = ? " +
"AND LVW_TEST_WT_WO_CONT = ? " +
"AND INERTIA_WT_CLASS = ? " +
"AND TEST_GROUP_ID = ? " +
"AND ENGINE_CODE = ? " +
"AND AXLE_RATIO = ?");
PreparedStatement statement = connection.createPreparedStatement(sql);
for (SubConfigurationDetailsObject test: subconfigListRLC) {
if (test.getFlag().equalsIgnoreCase("T")) {
statement.setIntParam(1, test.getSubConfigurationIndexNo());
statement.setIntParam(2, 0);
} else if (test.getFlag().equalsIgnoreCase("U")) {
statement.setIntParam(1, 0);
statement.setIntParam(2, test.getSubConfigurationIndexNo());
} else {
continue;
}
statement.set...Param(3, ...);
...
statement.executeUpdate();
}
You may probably only need one PreparedStatement, and the result is indeed faster.
BTW. better use StringBuilder instead of StringBuffer.