I have this SQL code generated by JHipster which I need to modify:
RowsFetchSpec<Employees> createQuery(Pageable pageable, Criteria criteria) {
List<Expression> columns = EmployeesSqlHelper.getColumns(entityTable, EntityManager.ENTITY_ALIAS);
columns.addAll(AccountsSqlHelper.getColumns(accountTable, "account"));
columns.addAll(RolesSqlHelper.getColumns(roleTable, "role"));
SelectFromAndJoinCondition selectFrom = Select
.builder()
.select(columns)
.from(entityTable)
.leftOuterJoin(accountTable)
......
.equals(Column.create("id", roleTable));
String select = entityManager.createSelect(selectFrom, Employees.class, pageable, criteria);
String alias = entityTable.getReferenceName().getReference();
String selectWhere = Optional.ofNullable(criteria).map(crit -> select + " " + "WHERE" + " " + alias + "." + crit).orElse(select); // TODO remove once https://github.com/spring-projects/spring-data-jdbc/issues/907 will be fixed
return db.sql(selectWhere).map(this::process);
}
private Employees process(Row row, RowMetadata metadata) {
Employees entity = employeesMapper.apply(row, "e");
entity.setAccount(accountsMapper.apply(row, "account"));
entity.setRole(rolesMapper.apply(row, "role"));
return entity;
}
As you can see the second Java method populates inner JSON code block.
I need to replace this code with this SQL code:
#Query("SELECT * " +
" FROM employees e " +
..........
"LIMIT :limit " +
"OFFSET :offset")
Flux<Employees> findAllByInstAccountParam(#Param("params") String params, #Param("limit") long limit, #Param("offset") long offset);
How I apply the Java method process after executing the SQL query for findAllByInstAccountParam?
Related
I need to pull few fields from entity class Employee and add few extra hard coded field and return the result using GROUP BY clause.
Below is the code I tried:
String query = "SELECT emp.category, emp.salary 0 as somevalue, 0 as dummy FROM employee emp "
+ "WHERE emp.date = :date AND emp.class = :class AND emp.classificationDetail.shortDescription = :classificationType GROUP BY emp.category";
TypedQuery<CustomEmployee> typQuery = entityManager.createQuery(query, CustomEmployee.class);
typQuery.setParameter("date", req.getDate());
typQuery.setParameter("class", req.getClass());
return typQuery.getResultList();
But I am getting exception that Cannot create TypedQuery for query with more than one return using requested result type.
How to achieve this.
Thanks.
First check this part: emp.salary 0 as somevalue. This should be either emp.salary as somevalue or 0 as somevalue, but not both.
Define a class like following (to keep it short; I use public properties, but you can change it if you want):
public class CustomEmployee {
public String category;
public Double salary;
public Double dummy;
...
}
The use it in the query as follows:
String query = "SELECT new mypackage.CategorySalary( " +
" emp.category, " +
" emp.salary as somevalue, " +
" 0 as dummy " +
") from ... " +
"WHERE ... ";
TypedQuery<CustomEmployee> typQuery = entityManager.createQuery(query, CustomEmployee.class);
I have scenario where I want to make dynamic query which myBatis supposed to support like as below :
<select id=“someRecords” resultMap=“someRecordMap”>
DROP TABLE IF EXISTS TEMP_TABLE;
CREATE TEMPORARY TABLE TEMP_TABLE(some_stub UUID);
INSERT INTO TEMP_TABLE (some_stub)
select regexp_split_to_table(#{someIds},',')::uuid;
SELECT wil.some_identifier_stub as identifier_stub
,wil.x
,wil.y
,wil.z
,wil.u
,wil.o
,msg.p
FROM TABLE_A msg
INNER JOIN TABLE_B wil ON msg.a_id = wil.b_id
INNER JOIN TABLE_C est ON est.c_stub = wil.b_stub
WHERE wil.unique_id = #{uniqueId} AND wil.b_type_id = #{b_TypeId}
<if test="environment != null">
<include refid="environmentCondition"></include>
</if>
</select>
<sql id="environmentCondition">
AND environment = #{environment}
</sql>
But instead of someRecordMap, I want to return DataSet so that it become backward compatible with my existing code
So instead of suing myBatis XMl approach I just make custom approach using reflection and annotations like below :
Section : Dynamic Query Based on some condition like IGNORE_SOME_JOIN, IGNORE_SOME_STUB, IGNORE_SOME_EXT_FLAG
#SqlQueries({#SqlQuery(name = "query1",
query = "select a,b," +
"c,d,e,f,g,wil.h," +
" j,h,i " +
START_DELIMITER + " " + IGNORE_SOME_JOIN + " " +
" ,some_message" + END_DELIMITER +
" FROM A_TABLE wil " +
START_DELIMITER + " " + IGNORE_SOME_JOIN + " " +
"LEFT OUTER JOIN B_TABLE wim on" +
" wil.unique_id = wim.unique_id" +
" and wim.created_date >= ? and wim.created_date <= ? " + END_DELIMITER +
" WHERE ( wil.created_date >= ? AND wil.created_date <= ? AND wil.primary_id = ? " +
START_DELIMITER + " " + IGNORE_SOME_STUB + " " +
" AND wil.unique_identifier_stub = ?::uuid " + END_DELIMITER +
START_DELIMITER + " " + IGNORE_SOME_EXT_FLAG +
" AND wil.some_ext_success_flag = ANY(?) " + END_DELIMITER + ")" +
"ORDER BY wil.created_date OFFSET ? LIMIT ? ")}
)
parsing logic for dynamic query be like :
abstract class ReportingQuery {
private static final Logger LOG = LoggerFactory.getLogger(ReportingQuery.class);
static final String START_DELIMITER = "#~";
static final String END_DELIMITER = "#~";
static final String REPLACEABLE_DELIMITER = "--";
/**
* Responsible to prepare final query after applying dynamic query criteria
*
* #param className : ReportingQuery class reference
* #param methodName : Query method name
* #param queryName : Dynamic query
* #param ignoreStrings : Criteria to be applied in dynamic query
* #return : final static query after applying dynamic query criteria(ignoreStrings)
*/
static Optional<String> getQuery(Class className, String methodName, String queryName, List<String> ignoreStrings) {
StringBuilder builder = new StringBuilder();
try {
Method[] methods = className.getDeclaredMethods();
Optional<String> queryString = Optional.empty();
if (Arrays.stream(methods).anyMatch(x -> x.getName().equals(methodName))) {
QueryExample.SqlQuery[] sqlQueries = Arrays.stream(methods)
.filter(x -> x.getName().equals(methodName))
.findFirst().get().getAnnotation(ReportingQuery.SqlQueries.class).value();
if (Arrays.stream(sqlQueries).anyMatch(x -> x.name().equals(queryName))) {
queryString = Optional.of(Arrays.stream(sqlQueries).filter(x -> x.name()
.equals(queryName)).findFirst().get().query());
}
}
String[] token = new String[0];
if (queryString.isPresent()) {
token = queryString.get().split(START_DELIMITER);
}
...... SOME logic to make query based on some dynamic condition
return Optional.of(builder.toString());
}
/**
*
*/
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
#interface SqlQuery {
String name();
String query();
}
/**
*
*/
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
#interface SqlQueries {
SqlQuery[] value();
}
}
So if I have condition IGNORE_SOME_JOIN then my final query with logic would be like
select a,b,c,d,e,f,g,wil.h,j,h,i FROM A_TABLE wil WHERE ( wil.created_date >= '2018-08-29T15:15:42.42'
AND wil.created_date <= '2018-08-30T15:15:42.42' AND wil.acct_id = 2000017
AND wil.unique_identifier_stub = 'a004f322-1003-40a7-a54b-f3b979744fd2'
AND wil.some_ext_success_flag = ANY('{"0","1"}')) ORDER BY wil.created_date OFFSET 0 LIMIT 500;
So with above I got query as string and I'll run below code and get Result Set :
PreparedStatement ps = con.prepareStatement(query)) {
prepareStatement(prepareStatementAndQueryList, ps, con);
try (ResultSet rs = ps.executeQuery()) {
DO SOMETHING WITH RESULT SET NOW
}
}
But I want to do this with MyBatis instead my own custom solution which would little error prone and might be not performance efficient as I have used much reflection here.
Yes, this is possible and is directly supported by mybatis. Here is an example from the documentation:
#SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(String name);
class UserSqlBuilder {
public static String buildGetUsersByName(final String name) {
return new SQL(){{
SELECT("*");
FROM("users");
if (name != null) {
WHERE("name like #{value} || '%'");
}
ORDER_BY("id");
}}.toString();
}
}
In the example above getUsersByName is a mapper method and UserSqlBuilder is used by the mapper to generate SQL query text dynamically based on mapper parameters. It's exactly what you need and very similar to what ReportingQuery does.
You need to adapt your code so that your existing query generator confirms to SelectProvider API but this seems to be rather straightforward.
I have following method to extract data from DB table
public User getNextUser() {
final EntityManager em = getEntityManagerFactory().createEntityManager();
final String sql = "UPDATE user " +
"SET processing = TRUE " +
"WHERE id = (" +
"SELECT id " +
"FROM user " +
"WHERE processing = FALSE " +
"LIMIT 1 FOR UPDATE) " +
"RETURNING *";
final Query query = em.createNativeQuery(sql, User.class);
User result = null;
try {
List userList = query.getResultList();
if (!userList.isEmpty()) {
result = (User) userList.get(0);
}
} finally {
em.close();
}
return result;
}
How it's possible to convert this native query to Hibernate query (to not invalidate 2nd level cache)? I have read that equivalent of
SELECT ... FOR UPDATE
in Hibernate it's possible to implement via
PESSIMISTIC_WRITE
I am developing an application that can update my database... However, I can't get my Java method working correctly. It gives me the following error: Must declare the scalar variable "#P0WHERE". Any suggestions?
The method I am using:
public void updateTable() throws SQLException
{
Scanner input = new Scanner(System.in);
System.out.println("Update the following:\n" + this);
this.getReservationInfo(input);
DataConnection connection = new DataConnection();
String query = "UPDATE " + TableName;
query += "Set Name = ?";
query += "WHERE Person_ID = " + id;
connection.updateData(query, person_name);
connection.closeConnection();
}
Add spaces before 'SET' and 'WHERE', otherwise it will not work.
String query = "UPDATE " + TableName;
query += " SET Name = ?";
query += " , Age = ?";
query += " , Col1 = ?"; //And other cols
query += " WHERE Person_ID = " + id;
EDIT: Changed query to update multiple columns.
I think so. You seem to be missing spaces. After the TableName and after the ?.
String query = "UPDATE " + TableName;
query += " Set Name = ?"; // tableSet not good, and
// ?WHERE is not valid add spaces.
query += " WHERE Person_ID = " + id;
public static final String UPDATE_DOCUMENTS_WITH_TO_DELETE_FLAG_FOR_USER_SQL = "\n" +
"UPDATE document d \n" +
"SET d.indexed = :flagValue \n" +
"WHERE d.user_id = :userId \n" +
"AND d.to_delete = :toDelete";
public static final String UPDATE_DOCUMENTS_WITH_TO_DELETE_FLAG_FOR_USER_WITH_EXCEPTIONS_SQL = "\n" +
"UPDATE document d \n" +
"SET d.indexed = :flagValue \n" +
"WHERE d.user_id = :userId \n" +
"AND d.to_delete = :toDelete \n" +
"AND d.id NOT IN (:exceptForDocuments)";
public int markUserDocumentsToDeleteAsUnindexed(String userId,Collection<String> exceptForDocuments) {
Map<String,Object> params = Maps.newHashMap();
params.put("flagValue",false);
params.put("userId",userId);
params.put("toDelete",1);
params.put("exceptForDocuments",exceptForDocuments);
if ( exceptForDocuments.isEmpty() ) {
return jdbcTemplate.update(UPDATE_DOCUMENTS_WITH_TO_DELETE_FLAG_FOR_USER_SQL, params);
}
else {
return jdbcTemplate.update(UPDATE_DOCUMENTS_WITH_TO_DELETE_FLAG_FOR_USER_WITH_EXCEPTIONS_SQL,params);
}
}
Is there a way to use a single query to perform both updates?
Because actually using the UPDATE_DOCUMENTS_WITH_TO_DELETE_FLAG_FOR_USER_WITH_EXCEPTIONS_SQL query seems to work against H2, but not MySQL.
Any idea to avoid this query duplication?
The problem is likely because not every driver can handle parameterized arrays/collections. If you have complete control over the exceptForDocuments contents, you can serialize it to SQL yourself (with simple sanitization checks) and then conditionally append it without using parameters.