How does Mybatis framework implement dynamic SQL?

One of the great features of MyBatis is its dynamic SQL. If you have experience with JDBC or other similar frameworks, you know how painful it can be to concatenate SQL statements based on different conditions. When concatenating, make sure you don’t forget the necessary Spaces and omit the comma at the end of the list of column names. You can take advantage of dynamic SQL to get rid of this pain. It uses OGNL (similar to EL expressions in JSP) expressions to do dynamic SQL concatenation to make it very easy.

Please click the following picture for more information (Swipe and add friends → Remark 66, please refuse to add if you don’t comment)

Practice the way of dynamic SQL

  • If conditional judgment

  • Choose, when, and otherwise selectors are used

  • trim, where, set

  • foreach

  • Use Ognl expressions

A case in field

If conditional judgment

What dynamic SQL typically does is conditionally include part of a WHERE clause. Such as:

<! -- Fuzzy matching -->    
<select id="queryUserByUserName" parameterType="string" resultType="user">    
    select id,userName,userPwd from user   where 1=1  
    <if test="userName! =null and userName! = "">      
        and userName like '%#{userName}%'       
    </if>
</select>
Copy the code

Using the if tag adds a test attribute, and concatenating and, or concatenating multiple conditions

Implementation method

@Override    
public List<User> queryUserByUserName(String userName) { 
    List<User> users=null;       
    SqlSession session=null;    
    try {           
        session=sqlSessionFactory.openSession();      
        Map map=new HashMap();
        / / map parameters
        map.put("userName",userName);
        users=session.selectList("com.xxx.mapper.UserMapper.queryUserByUserName", map);        
    } catch (Exception e) {       
        e.printStackTrace();     
    }finally{            
        if(null!=session){     
            session.close();      
        }            
    }      
    return users;   
}
Copy the code

[ImG-WJNQJJAS-1600915888331] [Img wJNqJJAS-1600915888331]

Run the result, SQL automatically judge and concatenate

Choose, when, and otherwise selectors are used

We don’t want to use all the conditional statements, just one or two of them. For this, MyBatis provides the Choose element, which is a bit like the Switch statement in Java

<select id="queryUserByParams" parameterType="map" resultType="user">        
    select id,userPwd       
    <choose>          
        <when test="nation! =null and nation! = "">      
            ,userName      
        </when>         
        <otherwise>     
            ,realName    
        </otherwise>       
    </choose>        
    from user      
    where userName like '%${userName}%'  
    <if test="phone! =null and phone! = "">   
        and phone like '%${phone}%'    
    </if>
</select>
Copy the code

If the nation is not empty then check the value of userName, otherwise check the value of realName

@Test    
public void test16(a){   
    UserDao userDao=new UserDaoImpl(sqlSessionFactory);    
    List<User> list=userDao.queryUserByParams("".null."xxx"); 
    for(User u:list){ System.out.println(u); }}Copy the code

trim, where, set

Now that the previous examples have nicely solved a notorious dynamic SQL problem, let’s look at the first configuration

<select id="findUserByUserName" resultMap="RM_User" >		
    select 			
    	userId, userName, password 		
    from 		
   	 	user 	
    where 		
    	userName like '%${userName}%'	
    <if test="phone ! = null and phone ! = "" >		
        and phone like '%${phone}%'	
    </if>	
</select>
Copy the code

UserName like ‘%${userName}%

<select id="findUserByUserName" resultMap="RM_User" >	
    select 			
    	userId, userName, password 		
    from 	
    	user         
    where	   
    <if test="userName ! = null and userName ! = "" >	
        userName like '%${userName}%'	  
    </if>	     
    <if test="phone ! = null and phone ! = "" >	
        and phone like '%${phone}%'		 
    </if>
</select>
Copy the code

In this case, we predict that the printed SQL will be

select userId, userName, password from user where 
Copy the code

Obviously this SQL will report an error

To solve this problem, we use the <where></where> tag

<select id="queryUserByParams" parameterType="map" resultType="user">
    select 
    	id,userPwd,phone
    <choose>  
        <when test="nation! =null and nation! = "">   
            ,userName   
        </when>   
        <otherwise>     
            ,realName   
        </otherwise>  
    </choose>from user<where>  
    <if test="userName ! =null and userName ! = "">    
        userName like '%${userName}%'  
    </if>  
    <if test="phone! =null and phone! = "">    
        and phone like '%${phone}%'    
    </if>
    </where>
</select>
Copy the code

Writing test classes

@Test  
public void test16(a){    
    UserDao userDao=new UserDaoImpl(sqlSessionFactory);   
    List<User> list=userDao.queryUserByParams("".""."");    
    for(User u:list){ System.out.println(u); }}Copy the code

The WHERE element knows to insert the “where” clause only if more than one if condition has a value. Also, if the final content begins with “AND” OR “, the WHERE element knows how to remove them. If my phone has a value and my userName doesn’t have a value, then WHERE also knows to remove the “and” from phone

However, if the WHERE element doesn’t behave the way it should, we can still customize the functionality we want by customizing the trim element. For example, the custom trim element equivalent to the WHERE element is:

<select id="queryUserByParams" parameterType="map" resultType="user">     
    select 
    	id,userPwd,phone       
    <choose>      
        <when test="nation! =null and nation! = "">      
            ,userName     
        </when>      
        <otherwise>           
            ,realName         
        </otherwise>          
    </choose>      
    from user      
    <trim prefix="where" prefixOverrides="and |or" >    
        <if test="userName ! =null and userName ! = "">  
            userName like '%${userName}%'   
        </if>         
        <if test="phone! =null and phone! = "">    
            and phone like '%${phone}%'        
        </if>                  
    </trim>            
</select>
Copy the code

This has the same effect as <where></where>

The prefixOverrides property ignores the sequence of text delimited by pipes (note that whitespace is also necessary in this example). The result is that all the content specified in the prefixOverrides attribute is removed and the content specified in the prefix attribute is inserted.

For the UPDATE statement, we use <set></set> to set the value

<update id="updateUserById" parameterType="user">    
    update user           
    <set>      
        <if test="userName! =null">   
            userName=#{userName},      
        </if>       
        <if test="userPwd! =null">   
            userPwd=#{userPwd},     
        </if>       
    </set>     
    where id=#{id}
</update>
Copy the code

Writing test methods

@Test   
public void test17(a){  
    UserDao userDao=new UserDaoImpl(sqlSessionFactory);
    User user=userDao.queryUserById(6);       
    user.setUserPwd(null);     
    user.setUserName("xxx06");  
    userDao.updateUserById(user);
}
Copy the code

If you’re interested in what the equivalent custom trim element looks like, this is what it looks like:

<update id="updateUserById" parameterType="user">       
    update user       
    <trim prefix="set" suffixOverrides="," > <! -->       
        <if test="userName! =null">      
            userName=#{userName},       
        </if>     
        <if test="userPwd! =null">    
            userPwd=#{userPwd},     
        </if>         
    </trim>        
    where id=#{id}
</update> 
Copy the code

This effect is identical to set

foreach

Another common requirement for dynamic SQL is the need to iterate over a collection, usually when building IN conditions or bulk inserts. Such as:

<select id="findUserByUserName" resultMap="RM_User" >	
    select 			
    	userId, userName, password 		
    from 		
    	user 		
    <where>			
        <if test="userNameList ! = null" >	
            userName in			
            <foreach item="item" index="index" collection="userNameList"open="(" separator="," close=")">	
                #{item}			
            </foreach>			
        </if>		
    </where>
</select>
Copy the code

Writing test methods

@Test
public void testFindUserByUserName(a) {	
    InputStream is = MybatisSecondaryCacheTest.class.getClassLoader().getResourceAsStream("mybatis.xml");		SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);			
    SqlSession session = sessionFactory.openSession();		
    // Create a parameter
    Map<String, Object> params = new HashMap<>();	
    // Create an array of strings and convert it to a list
    String[] userName = new String[]{"Tonygogo"."hello"."Ha ha ha."};				    						params.put("userNameList", Arrays.asList(userName)); 
    // String array to list, the name of the key must be consistent with the name of the variable in the mapping file
    List<User> users = session.selectList("findUserByUserName", params); 					 					System.out.println("Query result:" + users.toString());	
}
Copy the code

Use Ognl expressions

In the mapping above, if we use if to determine whether a value is empty or an empty string we do test=”userName! = null and userName ! *@Ognl@isNotEmpty(userName)* Ognl = @Ognl@isNotEmpty(userName)*

When using an OGNL expression, we add a Java class from ogNL to the root package, which has all kinds of judgments like null *@Ognl@isEmpty(userName), @Ognl@isNotEmpty(userName)** is an empty string @Ognl@isBlank(userName), @Ognl@isNot**Blank(userName)*, etc

We may be used to these four, it is just convenient for us to do some operations, will also be used in practice

import java.lang.reflect.Array;
import java.util.Collection;import java.util.Map;  
/** * the Ognl utility class is designed to reduce the long class name when Ognl expressions access static methods.@class@method(args) * * Example use: * <pre> * <if test="@Ognl@isNotEmpty(userId)"> 
*      and user_id = #{userId} 
*  </if> 
* </pre>
* 
*/
public class Ognl {        
    / * * * can be used to determine the String, Map, the Collection, whether the Array is empty@param o     
    * @return* /    
    @SuppressWarnings("rawtypes")    
    public static boolean isEmpty(Object o) throws IllegalArgumentException {        
        if(o == null) return true;         
        if(o instanceof String) {            
            if(((String)o).length() == 0) {return true; }}else if(o instanceof Collection) {    
            if(((Collection)o).isEmpty()){     
                return true; }}else if(o.getClass().isArray()) {  
            if(Array.getLength(o) == 0) {return true; }}else if(o instanceof Map) {   
            if(((Map)o).isEmpty()){    
                return true; }}else {    
            return false;
            // throw new IllegalArgumentException("Illegal argument type,must be : Map,Collection,Array,String. but was:"+o.getClass());
        }       
        return false;   
    }        
    / * * * can be used to determine the Map, a Collection, String, Array is not null@param c   
    * @return* /     
    public static boolean isNotEmpty(Object o) {    
        return! isEmpty(o); }public static boolean isNotBlank(Object o) {  
        return! isBlank(o); }public static boolean isBlank(Object o) {    
        if(o == null)       
            return true;    
        if(o instanceof String) {    
            String str = (String)o;      
            return isBlank(str);    
        }       
        return false; 
    }     
    public static boolean isBlank(String str) {    
        if(str == null || str.length() == 0) {   
            return true;       
        }           
        for (int i = 0; i < str.length(); i++) {     
            if(! Character.isWhitespace(str.charAt(i))) {return false; }}return true; }}Copy the code

extension

Annotated form dynamic SQL

In addition to XML configuration to support dynamic SQL, MyBatis provides various annotations such as @insertProvider, @updateProvider, @deleteProvider and @selectProvider to help build dynamic SQL statements. Then let MyBatis execute these SQL statements.

public interface AccountDao {

​    /** * addAccount record ** add string SQL provided by the AccountProvider class addAccount method ** returns the number of affected lines **@param account

​     \* @return* /@InsertProvider(method="addAccount",type=AccountProvider.class)public int  addAcccount(Account account);

​    

​    /** * addAccount record ** add string SQL provided by the AccountProvider class addAccount method ** returns primary key *@param account

​     \* @return* /@InsertProvider(method="addAccount",type=AccountProvider.class)@Options(useGeneratedKeys=true,keyColumn="id")public int  addAcccount02(Account account);

​    

​    /** * query account records by ID. * query string SQL provided by the queryAccountById method of the AccountProvider class@param id

​     \* @return* /@SelectProvider(method="queryAccountById",type=AccountProvider.class)public Account queryAccountById(@Param("id")int id);

​    

​    /** * multiple conditional query account records ** Query string SQL provided by the queryAccountByParams method of the AccountProvider class@param aname

​     \* @param type

​     \* @param time

​     \* @return* /@SelectProvider(method="queryAccountByParams",type=AccountProvider.class)public List<Account> queryAccountByParams(@Param("aname")String aname,@Param("type")String type,@Param("time")String time);

​    

​    /** * update account records ** Update string SQL provided by the updateAccountById method of the AccountProvider class@param account

​     \* @return* /@UpdateProvider(method="updateAccount",type=AccountProvider.class)public int updateAccountById(Account account);

​    

​    /** * deleteAccount records by id. * delete string SQL provided by the AccountProvider class deleteAccount method@param id

​     \* @return* /@DeleteProvider(method="deleteAccount",type=AccountProvider.class)public int deleteAccountById(@Param("id")int id);

}

 

 

public class AccountProvider {

​    /** * returns add account record SQL string \*@param account

​     \* @return* /public String addAccount(final Account account){

​        return new SQL(){{

​            INSERT_INTO("account");

​            VALUES("aname"."#{aname}");

​            VALUES("type"."#{type}");

​            VALUES("remark"."#{remark}");

​            VALUES("money"."#{money}");

​            VALUES("user_id"."#{userId}");

​            VALUES("create_time"."#{createTime}");

​            VALUES("update_time"."#{updateTime}");

​        }}.toString();

​    }

​    

​    /** * returns the SQL string \* to query account records by ID@param id

​     \* @return* /public String queryAccountById(@Param("id")int id){

​        return new SQL(){{

​            SELECT("id,aname,type,remark,create_time as createTime,update_time as updateTime,user_id as userId");

​            FROM("account");

​            WHERE(" id=#{id} ");

​        }}.toString();

​    }

​    

​    /** * return multiple conditional query SQL string \*@param aname

​     \* @param type

​     \* @param time

​     \* @return* /public String queryAccountByParams(@Param("aname") final String aname,@Param("type")final String type,@Param("time")final String time){

​        String sql= new SQL(){{

​            SELECT("id,aname,type,remark,create_time as createTime,update_time as updateTime,user_id as userId");

​            FROM("account");

​            WHERE(" 1=1 ");

​            if(! StringUtils.isNullOrEmpty(aname)){ AND(); WHERE(" aname like concat('%',#{aname},'%') ");

​            }

​            if(! StringUtils.isNullOrEmpty(type)){ AND(); WHERE(" type =#{type}");

​            }

​            if(! StringUtils.isNullOrEmpty(time)){ AND(); WHERE(" create_time <=#{time}");

​            }

​        }}.toString();

​        return sql;

​    }

​    

​    /** * returns the update account record SQL string \*@param account

​     \* @return* /public String updateAccount(Account account){

​         return new SQL(){{

​               UPDATE(" account");

​               SET("aname=#{aname}");

​               SET("type=#{type}");

​               WHERE("id=#{id}");

​         }}.toString(); 

​    }

​    

​    /** * returns the SQL string \* for deleting account records@param id

​     \* @return* /public String deleteAccount(@Param("id")int id){

​        return new SQL(){{

​            DELETE_FROM("account");

​            WHERE("id=#{id}"); }}.toString(); }}Copy the code

;

​ }

/ * *

* Returns an UPDATE account record SQL string

​ * @param account

​ * @return

* /

​ public String updateAccount(Account account){

​ return new SQL(){{

​ UPDATE(” account”);

​ SET(“aname=#{aname}”);

​ SET(“type=#{type}”);

​ WHERE(“id=#{id}”);

​ }}.toString();

​ }

/ * *

* Returns the SQL string for deleting account records

​ * @param id

​ * @return

* /

​ public String deleteAccount(@Param(“id”)int id){

​ return new SQL(){{

​ DELETE_FROM(“account”);

​ WHERE(“id=#{id}”);

​ }}.toString();

​ }

}




Copy the code