Reusing dynamic sql fragments - mybatis

Hei there, I'm working on a Primefaces app and as a persistence layer I chose Mybatis.
This is how a regular sql would look in my mapper:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
<if test="( hasFilters == 'yes' ) and ( parameters != null )">
<where>
<foreach item="clause" collection="parameters" separator=" AND "
open="(" close=")">
UPPER(${clause.column}) ${clause.operator} #{clause.value}
</foreach>
</where>
</if>
<if test="sort == 'true'">
ORDER BY ${sortField}
<if test="sortOrder == 'DESC'"> DESC</if>
<if test="sortOder == 'ASC'"> ASC</if>
</if>
</select>
Almost all my queries use the dynamic sql part starting from the <if test...>. Is it possible to put it in a separate file and then reuse it all over my queries?

There are several options how to reuse sql snippets.
SQL snippets and include
The first one is using include. Create separate mapper Common.xml:
<mapper namespace="com.company.project.common">
<sql id="orderBy>
<if test="sort == 'true'">
ORDER BY ${sortField}
<if test="sortOrder == 'DESC'"> DESC</if>
<if test="sortOder == 'ASC'"> ASC</if>
</if>
</sql>
<sql id="filters">
<if test="( hasFilters == 'yes' ) and ( parameters != null )">
<where>
<foreach item="clause" collection="parameters" separator=" AND "
open="(" close=")">
UPPER(${clause.column}) ${clause.operator} #{clause.value}
</foreach>
</where>
</if>
</sql>
</mapper>
And the use it in other mappers MyMapper.xml:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
<include refid="com.company.project.common.filters"/>
<include refid="com.company.project.common.orderBy"/>
</select>
To avoid duplicating namespace in every include you can create shortcut snippets in MyMapper.xml:
<sql id="orderBy">
<include refid="com.company.project.common.orderBy"/>
</sql>
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
<include refid="orderBy"/>
</select>
Mybatis-velocity macro
Another possible option is to use mybatis scripting. Using mybatis-velocity scripting engine you can define velocity macro and use it like this.
In Commons.xml:
<sql id="macros"
#macro(filters)
#if ( $_parameter.hasFilters )
#repeat( $_parameter.parameters $clause "AND" " (" ")" )
${clause.column} ${clause.operator} #{clause.value}
#end
#end
#end
#macro(order_by)
..
#end
</sql>
In MyMapper.xml:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
<include refid="macros"/>
SELECT * FROM SSLS_GUI.VW_TU
#filters()
#order_by()
</select>
Including macros via sql snippet is not the most clean way to reuse macros. It is just an idea how this is used.
Much better option is to configure mybatis-velocity and specify what global macros are available. In this case there will be no need to include macros snippet and result query will be like this:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
#filters()
#order_by()
</select>

See #Roman Konoval's answer for how to do this in XML.
For another option on the pure Java side (in OP's case, the XML option above is more applicable; I leave this here for those who may be using pure Java), one can use Mybatis' Statement Builders, which allow for the construction of dynamic SQL inline with Java code, you can factor out the common code there similar to the way you would factor out any common code.
The example they give in the Mybatis doc is as follows:
private String selectPersonSql() {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
FROM("PERSON P");
FROM("ACCOUNT A");
INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
WHERE("P.ID = A.ID");
WHERE("P.FIRST_NAME like ?");
OR();
WHERE("P.LAST_NAME like ?");
GROUP_BY("P.ID");
HAVING("P.LAST_NAME like ?");
OR();
HAVING("P.FIRST_NAME like ?");
ORDER_BY("P.ID");
ORDER_BY("P.FULL_NAME");
}}.toString();
}
So, you could define a function that factors out your commmon dynamic SQL from your XML, and perhaps takes arguments representing the SELECT columns and FROM table portions of the statement (and anything else that might vary), which can then be passed in to the factored-out dynamic SQL methods inside of the function.

Related

Material UI Select component throwing javascript console warning

i am using select component with values by setting them using useEffect like below.
useEffect(()=>{
const {property, properties} = props;
setProperties(properties); // properties is an array of values
['somevalue1', 'somevalue2']
setProperty(property)
},[props])
but this leads to following javascript warning. i guess this because of race condition where property is set first rather than properties array. any tips to avoid warning?
You have provided an out-of-range value somevalue1 for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are "".
below is the code for select that i am using.
<Select
value={property}
onChange={onChange}
input={<BootstrapInput />}
defaultValue=""
>
{Object.keys(properties).map((key) => (
<MenuItem key={key} value={key}>
{key}
</MenuItem>
))}
</Select>
Why you set state value in useEffect? If you don't have a specific reason, try modifying it like the code below.
const [properties, setProperties] = useState(props.properties);
const [preperty, setProperty] useState(props.property)
And the code you asked seems to have the potential for infinite repetition.

mybatis use input param as argument in constructor

In mybatis, can i use an input #Param object in the resultMap constructor?
int test(#Param("param1") someObj obj, #Param("str") String str);
<resultMap id="testResultMap" type="com.test.someOtherObject">
<constructor>
<idArg column="id" javaType="String"/>
<arg column="<use the input param obj of type someObj>" javaType="com.test.someObj"/>
No: Parameters and result maps are 2 distinct things. You cannot feed the result map directly from parameter.
If you want the input parameter to be in the result Map, then it must be in the result set, so you must either select the corresponding column if you filter on that column: SELECT col FROM t WHERE col = #{param}or add it as pseudo column: SELECT '${param}' AS "colName" FROM t and map it as usual with parameterized constructor if you like.
If the constructor argument is a complex type then use <arg resultMap="anotherResultMap" /> instead of <arg column="col" />.

Safe way to prevent sql injection with mybatis

I am using '$' notation in mybatis query:
<select id="queryOrgs" resultMap="orgLiteMap" parameterType="gov.cbrc.gzbanking.data.QueryRequest" >
select id, name from sys_orgs
<if test="filter != null">where id like #{filter, jdbcType=INTEGER} or name like #{filter, jdbcType=INTEGER}</if>
<if test="order != null">order by ${order}</if>
limit #{offset, jdbcType=INTEGER},#{fetch, jdbcType=INTEGER}
</select>
the order parameter can be something like "id desc", do I need to worry about sql injection here? We know that mybatis uses PreparedStatement, if mybatis call PreparedStatement#executeQuery() against "select" statement, or the jdbc driver implementation does't allow multiple statements in one call, then sql injection is impossible, am I right?
If that is possible for sql injection in my case, what's the verified way to prevent it ?
------------------------ edit -----------------------
Is that enough to check order parameter has a sql delimiter?
If order comes directly from the user, you can get into trouble. You should never trust user input.
To respond to your questions:
If you don't allow multiple queries you will get an exception if you try running more than one, yes;
checking for an SQL delimiter might be enough, might not;
Using ${} exposes you to SQL injection if you send the user input unchanged to the SQL. The above two methods are fragile:
you can easily forget to configure the connection or a colleague of yours might turn it on because he/she needs to execute multiple queries in one statement and they were not aware about this particular statement
hackers might be smarter than checks you have in place (e.g have you considered testing for \u003B too? What else might an attacker attempt?).
Always perform your own escapes and checks and send a value you control, not something received from the user. Have all the correct values in your code and send that instead. It's then just a matter of determining which one the user wanted and fallback to a default if you can't determine it, something like:
String userChoice = order.toLowerCase();
String safeOrder = null;
if ("id desc".equals(userChoice)) {
safeOrder = "id desc";
} else if ("id asc".equals(userChoice)) {
safeOrder = "id asc";
....
....
} else if ("name desc".equals(userChoice)) {
safeOrder = "name desc";
} else {
// if no clean match then maybe user tried something fishy...
// ... go with some default
safeOrder = "id desc"; // or null or whatever you like...
}
then in your mapping you do:
<if test="safeOrder != null">order by ${safeOrder}</if>
See https://mybatis.github.io/mybatis-3/sqlmap-xml.html. section 'String Substitution' for offical comment on that.
It's not safe to accept input from a user and supply it to a statement
unmodified in this way. This leads to potential SQL Injection attacks
and therefore you should either disallow user input in these fields,
or always perform your own escapes and checks.
My answer, mybatis sql template may be a good choose. FYI:
<sql id="orderTypeSql">
<trim prefix=" ">
<if test="'desc'.equalsIgnoreCase(orderType)">desc</if>
</trim>
</sql>
<sql id="orderColumnSql">
<trim prefix="order by " suffix="" >
<choose>
<when test="orderColumn==null or orderColumn==''"></when>
<when test="orderColumn=='id'">
id<include refid="orderTypeSql"/>
</when>
<when test="orderColumn=='name'">
`name`<include refid="orderTypeSql"/>
</when>
</choose>
</trim>
</sql>
<select id="testOrderBy" resultType="User">
select
id,
`name`
from t_user
<include refid="orderColumnSql"/>
limit 0, 10
</select>

Submitting forms containing multiple selects in Play Framework for Scala

I have a multiple select item in a Form. According to Play Framework's documentation, I need 'repeated values' to make all the selected options fit into a List[String] property of my data structure.
case class MyFormData (
fq_cset: Option[List[String]]
)
val myForm: Form[MyFormData] = Form(
mapping(
"fq_cset" -> optional(list(text))
)
)(MyFormData.apply _)(MyFormData.unapply _)
I quote Play's documentation:
When you are using repeated data like this, the form values sent by the browser must be named emails[0], emails[1], emails[2], etc.
I cannot figure out how to name the values like mentioned above. I tried to create a select element like this one
<select name="fq_cset">
<option name="fq_cset[0]" value="A" selected="selected">Value A</option>
<option name="fq_cset[1]" value="B" >Value B</option>
<option name="fq_cset[2]" value="C" selected="selected">Value C</option>
<option name="fq_cset[3]" value="D" >Value D</option>
</select>
but after submitting the form I see in the URL
/path?fq_cset=A&fq_cset=C
instead of
/path?fq_cset[0]=A&fq_cset[2]=C
The absence of the indexes enclosed in square brackets, prevents the correct binding of the parameters in the List[String] property named fq_cset in MyFormData class.
How can I get this to work properly? Is this the right approach to get what I need or I misunderstood the documentation?

Use Struts if Statement inside iterator

I really need your help. I have the following Struts2 iterator?
<s:iterator value="familiari" status="entry">
</s:iterator>
How can I test the addess propery is empty??
the following does not work
<s:if test="#entry.addess!=''">
</s:if>
It seems you are misundestanding the meaning of the status propery of the iterator tag: that's an special iterator object to track row number (odd/even checks, etc).
You should use the var property. For example (not tested) :
<s:iterator value="familiari" var="myobj">
<s:if test="#myobj.addess != ''">
</s:if>
<s:iterator>