MyBatis from entry to soil-dynamic SQL

This is the tenth article in the mybatis series. If you don’t read the previous suggestions, go to the [Java Tsuka Fox] public account to view the previous article, which is convenient for understanding and mastering. In the previous article, I focused on the related knowledge of lazy loading, discriminator and inheritance.

In this article we introduce a more powerful function of mybatis. This function is dynamic sql, especially when dealing with sql splicing, it is our gospel. Basically all the pain points of SQL splicing, mybatis has helped us solve it.

So let's learn about the usage of various dynamic sql in mybatis.

Create a database

The first is to create the database for our demonstration, as shown below:

DROP DATABASE IF EXISTS `mybatisdemo`;
CREATE DATABASE `mybatisdemo`;
USE `mybatisdemo`;
DROP TABLE IF EXISTS user;
CREATE TABLE user(
  id int AUTO_INCREMENT PRIMARY KEY COMMENT '用户id',
  name VARCHAR(32) NOT NULL DEFAULT '' COMMENT '用户名',
  age SMALLINT NOT NULL DEFAULT 1 COMMENT '年龄'
) COMMENT '用户表';
INSERT INTO t_user VALUES (1,'Java冢狐',18),(2,'java',100),(3,'冢狐',23);

if element

The first introduction is the if element of dynamic sql, this if element is equivalent to the if judgment in java.

Its usage syntax:

<if test="判断条件">
需要追加的sql
</if>

The value of test is a judgment expression, written in the way of OGNL expression, when the test is established, the sql inside the if body will be spliced.

Such as:

<select id="getList1" resultType="com.zhonghu.chat10.demo8.model.UserModel" parameterType="map">
    SELECT id,name,age FROM user
    WHERE 1 = 1
    <if test="id!=null">
        AND id = #{id}
    </if>
    <if test="name!=null and name.toString()!=''">
        AND name = #{name}
    </if>
    <if test="age!=null">
        AND age = #{age}
    </if>
</select>

The above query user list, the parameter is a map, when the id in the map is not empty, it will be used as the condition to query, if the name is not empty, the name will also be used as the condition, if the age is not empty, the age will also be used as the condition Inquire

When only the id is passed in, the sql is as follows:

SELECT id,name,age FROM user WHERE 1 = 1 AND id = ?

When all three parameters are passed, the sql is as follows:

SELECT id,name,age FROM user WHERE 1 = 1 AND id = ? AND name = ? AND age = ?

Compared with the Java code, does the above writing look a lot more refreshing and more convenient to maintain. Everyone pay attention to the WHERE 1=1 in sql. If there is no such, the above is not easy to achieve through the if element alone. Mybatis also has it. The solution will be explained later.

choose/when/otherwise元素

After introducing the if similar to java, there must be corresponding if-else statements, namely choose, when and otherwise, the specific syntax:

<choose>
    <when test="条件1">
        满足条件1追加的sql
    </when>
    <when test="条件2">
        满足条件2追加的sql
    </when>
    <when test="条件n">
        满足条件n追加的sql
    </when>
    <otherwise>
        都不满足追加的sql
    </otherwise>
</choose>

If one of the conditions inside choose is satisfied, the SQL splicing inside choose will end.

otherwise is optional. When all conditions are not met, otherwise will take effect.

Such as:

Pass in id, name, age as conditions and judge in order. If id is not empty, use id as condition and ignore other conditions. If id is empty, it will judge whether name is empty, and if name is not empty, use name as condition If the name is empty, then check whether age is empty. If age is not empty, use age as the condition.

<select id="getList2" resultType="com.zhonghu.chat10.demo8.model.UserModel" parameterType="map">
    SELECT id,name,age FROM user
    WHERE 1 = 1
    <choose>
        <when test="id!=null">
            AND id = #{id}
        </when>
        <when test="name!=null and name.toString()!=''">
            AND name = #{name}
        </when>
        <when test="age!=null">
            AND age = #{age}
        </when>
    </choose>
</select>

If the id, name, and age are all passed, the sql is as follows:

SELECT id,name,age FROM t_user WHERE 1 = 1 AND id = ?

If the value is passed name, age, sql is as follows:

SELECT id,name,age FROM t_user WHERE 1 = 1 AND name = ?

The name is judged before age, so the name condition is matched first.

where element

The where 1=1 part of the code is in the SQL of the above two cases. Although it can solve the problem, it does not look beautiful. If the part 1=1 in where 1=1 is eliminated, the above two cases will have problems. , There will be an AND symbol after where. This problem has been considered in mybatis. It is a universal problem. Mybatis is solved by the where element. When the where element is used, mybatis will process the sql spliced ​​inside where , Remove the AND or OR in front of this part of sql, and append a where in front. We use the where element to transform the case 1 above, as follows:

<select id="getList1" resultType="com.zhonghu.chat10.demo8.model.UserModel" parameterType="map">
    SELECT id,name,age FROM user
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
    </where>
</select>

where 1=1 is replaced with a where element.

When the id and name are passed in, the sql inside where will become like this:

AND id = ? AND name = ?

mybatis will process the above sql, remove the AND in front, and append a where in front, it becomes the following

where id = ? AND name = ?

Case 2 also uses where to transform it into the following:

<select id="getList2" resultType="com.zhonghu.chat10.demo8.model.UserModel" parameterType="map">
    SELECT id,name,age FROM user
    <where>
        <choose>
            <when test="id!=null">
                AND id = #{id}
            </when>
            <when test="name!=null and name.toString()!=''">
                AND name = #{name}
            </when>
            <when test="age!=null">
                AND age = #{age}
            </when>
        </choose>
    </where>
</select>

Does it look a lot more comfortable now?

set element

Now we want to update the user information by the user id, the parameter is the UserModel object, if the properties in the object are not empty, update it, we can write like this:

<update id="update1" parameterType="com.zhonghu.chat10.demo8.model.UserModel">
    UPDATE user SET
    <if test="name!=null">
        name = #{name},
    </if>
    <if test="age!=null">
        age = #{age},
    </if>
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
    </where>
</update>

Let's take a look, when all attributes are passed, sql becomes the following:

UPDATE user SET name = ?, age = ?, where id = ?

The above sql is problematic. There is an extra comma in front of where. You have to find a way to remove this comma. This comma belongs to the comma after the last field that needs to be updated. It is redundant. Mybatis provides a set element to solve this problem. , Change the above code to the following:

<update id="update1" parameterType="com.zhonghu.chat10.demo8.model.UserModel">
    UPDATE user
    <set>
        <if test="name!=null">
            name = #{name},
        </if>
        <if test="age!=null">
            age = #{age},
        </if>
    </set>
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
    </where>
</update>

We removed the set in sql and added a set element. The set element will process the internally spliced ​​sql, remove the comma before and after this part of sql, and add set in front.

When id and age are passed in, the generated sql:

UPDATE user SET age = ? where id = ?

trim element

The function of this element is relatively powerful, let's take a look at his grammar:

<trim prefix="" prefixOverrides="" suffix="" suffixOverrides="">
</trim>

The trim element can contain various dynamic SQL, such as where, choose, sql and other elements. Using the elements contained in trim, the mybatis processing process:

  • First splicing the sql inside trim, for example, this part of sql is called sql1
  • Remove the part specified by prefixOverrides of trim in the part in front of the sql1 string to get sql2
  • Remove the part specified by suffixOverrides of trim in the part after the sql2 string to get sql3
  • Append the value specified by prefix in trim before sql3 to get sql4
  • Append the value specified by suffix in trim after sql4 to get the final sql5 that needs to be spliced

After understanding this process, it is explained that trim can be used to replace where and set. We use trim to transform Case 1, as follows:

<select id="getList1" resultType="com.zhonghu.chat10.demo8.model.UserModel" parameterType="map">
    SELECT id,name,age FROM user
    <trim prefix="where" prefixOverrides="and|or">
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
    </trim>
</select>

Pay attention to the way of writing the value of prefixOverrides above. If there are multiple ones that need to be covered, use | to separate them. The way of writing suffixOverrides is similar to that of prefixOverrides.

We are using trim to transform the above update, as follows:

<update id="update1" parameterType="com.zhonghu.chat10.demo8.model.UserModel">
    UPDATE user
    <trim prefix="SET" prefixOverrides="," suffixOverrides=",">
        <if test="name!=null">
            name = #{name},
        </if>
        <if test="age!=null">
            age = #{age},
        </if>
    </trim>
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
    </where>
</update>

The prefixOverrides and suffixOverrides above are both set with commas, which means that the commas before and after the sql inside the trim will be removed, and finally a set specified by the prefix will be spliced ​​in front.

In fact, the implementation of where and set are inherited from TrimSqlNode, the java code corresponding to where:

public class WhereSqlNode extends TrimSqlNode {
  private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
  public WhereSqlNode(Configuration configuration, SqlNode contents) {
    super(configuration, contents, "WHERE", prefixList, null, null);
  }
}

Java code corresponding to set:

public class SetSqlNode extends TrimSqlNode {
  private static final List<String> COMMA = Collections.singletonList(",");
  public SetSqlNode(Configuration configuration,SqlNode contents) {
    super(configuration, contents, "SET", COMMA, null, COMMA);
  }
}

In the end, they all rely on TrimSqlNode to achieve.

foreach element

It is equivalent to a loop in java, which can be used to traverse arrays, collections, maps, etc.

grammar

<foreach collection="需要遍历的集合" item="集合中当前元素" index="" open="" separator="每次遍历的分隔符" close="">
动态sql部分
</foreach>
  • collection: can be a List, Set, Map or array
  • item: a reference to the current element in the collection
  • index: used to access the position of the current element in the collection
  • separator: the separator between each element
  • Open and close are used to configure what prefix and suffix are used to wrap all the spliced ​​SQL inside foreach.

Case: in multi-value query

We make a modification to Case 1. The map supports placing the user's id list (ArrayList), and the corresponding key is idList, and then supports multiple user id queries. At this time, we need to use in to query. The implementation is as follows:

<select id="getList1" resultType="com.zhonghu.chat10.demo8.model.UserModel" parameterType="map">
    SELECT id,name,age FROM user
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
        <if test="idList!=null and idList.size()>=1">
            <foreach collection="idList" item="id" open="AND id in (" separator="," close=")">
                #{id}
            </foreach>
        </if>
    </where>
</select>

Look at the part of idList above, and judge that this parameter is not empty, and size() is greater than 1, indicating that the collection is not empty, and then the foreach element inside the if element will be taken.

For example, the idList we passed corresponds to [1,2], and the resulting sql is as follows:

SELECT id,name,age FROM t_user WHERE id in ( ? , ? )

Case: Bulk Insert

Pass in the UserModel List collection, and use foreach to implement batch insertion, as follows:

<insert id="insertBatch" parameterType="list">
    INSERT INTO user (id,name,age) VALUES
    <foreach collection="collection" separator="," item="item">
        (#{item.id}, #{item.name}, #{item.age})
    </foreach>
</insert>

Test case

com.zhonghu.chat10.demo8.Demo8Test#insertBatch
@Test
public void insertBatch() throws IOException {
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<UserModel> userModelList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            userModelList.add(UserModel.builder().id(10 + i).name("mybatis-" + i).age(20 + i).build());
        }
        int count = mapper.insertBatch(userModelList);
        log.info("{}", count);
    }
}

Pass in 3 user information, run output

39:52.241 [main] DEBUG c.j.c.d.m.UserMapper.insertBatch - ==>  Preparing: INSERT INTO user (id,name,age) VALUES (?, ?, ?) , (?, ?, ?) , (?, ?, ?) 
39:52.317 [main] DEBUG c.j.c.d.m.UserMapper.insertBatch - ==> Parameters: 10(Integer), mybatis-0(String), 20(Integer), 11(Integer), mybatis-1(String), 21(Integer), 12(Integer), mybatis-2(String), 22(Integer)
39:52.327 [main] DEBUG c.j.c.d.m.UserMapper.insertBatch - <==    Updates: 3
39:52.327 [main] INFO  c.j.chat05.demo8.Demo8Test - 3

sql/include element

These two elements are generally used in conjunction to achieve the effect of code reuse.

The sql element can be used to define a section of dynamic sql, the syntax is as follows:

<sql id="sql片段id">
各种动态sql
</sql>

When you need to use it in other places, you need to introduce it through the include keyword:

<include refid="需要引入的sql片段的id"/>

The refid value is written, the value of refid is the value of the namespace of the mapper xml. The id of the sql. If in the same mapper, the namespace can be omitted, just write the id of the corresponding sql, such as:

<include refid="findSql"/>
<include refid="com.javacode2018.chat05.demo8.mapper.UserMapper.findSql"/>

E.g:

Two queries are defined below. Their query conditions are the same. Finally, the conditions are extracted and a fragment is defined with the sql element, and then shared.

<sql id="findSql">
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
        <if test="idList!=null and idList.size()>=1">
            <foreach collection="idList" item="id" open="AND id in (" separator="," close=")">
                #{id}
            </foreach>
        </if>
    </where>
</sql>
<select id="getList1" resultType="com.zhonghu.chat10.demo8.model.UserModel" parameterType="map">
    SELECT id,name,age FROM user
    <include refid="com.zhonghu.chat10.demo8.mapper.UserMapper.findSql" />
</select>
<select id="getList1Count" resultType="com.zhonghu.chat10.demo8.model.UserModel" parameterType="map">
    SELECT count(*) FROM user
    <include refid="findSql" />
</select>

bind element

The bind element allows us to customize a variable in the context through an ognl expression, and finally this variable can be used in dynamic SQL.

grammar

<bind name="变量名称" value="ognl表达式">

Case study

Extend the case in sql and include, add a fuzzy query based on user name, the key corresponding to the user name in the map is likeName, mainly modify the above sql fragment part, and add the following part in sql:

<if test="likeName!=null and likeName.trim()!=''">
  <bind name="nameLike" value="'%'+likeName.trim()+'%'" />
  AND name like #{nameLike}
</if>

First judge whether the passed parameter likeName is not an empty string, and then use the bind element to create a variable nameLike with a value of'%'+likeName.trim()+'%'.

test:

com.zhonghu.chat10.demo8.Demo8Test#getModelList
@Test
public void getModelList() throws IOException {
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("likeName","java");
        List<UserModel> userModelList = mapper.getList1(paramMap);
        log.info("{}", userModelList);
    }
}

Run output:

06:25.633 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM user WHERE name like ? 
06:25.671 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: %java%(String)
06:25.690 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 1
06:25.691 [main] INFO  c.j.chat05.demo8.Demo8Test - [UserModel(id=1, name=Java冢狐, age=18)]

Note the second part of the output, the parameter value is %java%.

# And $

# And $ are generally used in combination with variables, such as #{}, ${}.

#{}: Is the parameter placeholder? , That is, SQL precompilation, which is equivalent to using the SQL placeholder in PreparedStatement in jdbc, which can prevent SQL injection

${}: is string replacement, that is, string splicing, and SQL injection cannot be accessed.

The usage of #{} has many cases above, here we come to a case of ${}.

Next, pass in any sort sql through the orderSql variable, as follows:

<select id="getList1" resultType="com.zhonghu.chat10.demo8.model.UserModel" parameterType="map">
    SELECT id,name,age FROM t_user
    <if test="orderSql">
        ${orderSql}
    </if>
</select>

Incoming value:

orderSql = "order by id asc,age desc"

The sql generated by the final run is as follows:

20:32.138 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM user order by id asc,age desc 
20:32.173 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
20:32.196 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 6
20:32.197 [main] INFO  c.j.chat05.demo8.Demo8Test - [UserModel(id=1, name=java冢狐, age=18), UserModel(id=2, name=java, age=100), UserModel(id=3, name=冢狐, age=23), UserModel(id=10, name=mybatis-0, age=20), UserModel(id=11, name=mybatis-1, age=21), UserModel(id=12, name=mybatis-2, age=22)]

Mybatis will replace the $ containing part with sql.

to sum up

This article focuses on how to use MyBatis's dynamic SQL, focusing on the elements that rely on MyBatis, such as: if, choose, where, set, trim, foreach, bind and other elements. Detailed examples and codes are given for each element to demonstrate.

At last

  • If you feel that you are rewarded after reading it, I hope to pay attention to it. By the way, give me a thumbs up. This will be the biggest motivation for my update. Thank you for your support.
  • Welcome everyone to pay attention to my public account [Java Fox], focusing on the basic knowledge of java and computer, I promise to let you get something after reading it, if you don’t believe me, hit me
  • Seek one-click triple connection: like, forward, and watching.
  • If you have different opinions or suggestions after reading, please comment and share with us. Thank you for your support and love.

——I am Chuhu, and I love programming as much as you.

Welcome to follow the public account "Java Fox" for the latest news

Guess you like

Origin blog.csdn.net/issunmingzhi/article/details/114107285