Minimalist Mybatis Tour (1): CRUD

Keywords: xml Mybatis MySQL JDBC

abstract

Recently, mybatis framework has been studied. As an excellent ORM framework, many excellent design ideas of mybatis are worth learning.

The configuration files of mybatis are mainly config and mapper. Config defines global parameters: data source type (POOL, UNPOOLED, JNDI), transaction management type (default managed), library url, account information, and mapper file path.

config configuration

// config.xml
<configuration>
    <environments default="classfly">
        <environment id="classfly">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8" />
                <property name="username" value="yourusername" />
                <property name="password" value="yourpassword" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/UserMapper.xml" />
    </mappers>
</configuration>

DBNAME='test', which is the default library allocated by mybatis:

mapper configuration

First of all, we should ask ourselves the following three questions:
- Why configure mapper files?
- How to configure mapper files?
- How does mybatis parse mapper files?

The role of mapper files

The mapper file provides a "communication protocol" between the persistence layer and the application layer. It maintains the relationship between the application code DO object and the persistent stored data through mysql keywords resultMap, parameterType, etc.

The mapper file defines four commonly used DML statements: select, insert, update, delete, and defines statement={mapper namespace}.{operation} to implement calling DB operations at the application code level.

Configure mapper file

The key parts of the mapper configuration file used in the test case are given here.

<!-- Code list-1 -->
<mapper namespace="com.classfly.mapper.UserMapper">
    <resultMap id="user" type="pojo.User">
        <result column="user_id" property="userId" />
        <result column="user_name" property="userName" />
        <result column="password" property="password" />
        <result column="age" property="age" />
    </resultMap>

    <select id="query" resultMap="user">
        SELECT * FROM user;
    </select>

    <insert id="insert" parameterType="pojo.User">
        insert into user
          (
            user_id,
            user_name,
            password,
            age)
        values
          (
            #{userId},
            #{userName},
            #{password},
            #{age}
          )
        <selectKey resultType="pojo.User" keyProperty="id" order="AFTER">
            select LAST_INSERT_ID() as id
        </selectKey>
    </insert>

    <update id="update" parameterType="pojo.User">
        UPDATE user
        SET user_name = #{userName}
          , password = #{password}
          , age = #{age}
        WHERE user_id = #{userId}
    </update>

    <delete id="delete">
        DELETE FROM user
        WHERE user_id = #{userId}
    </delete>
</mapper>

The mapper namespace defines the scope of DML statements. What happens if two different mapper files define the same namespace? Because mysql maintains the global uniqueness of namespace, when parsing mapper files, mysql throws an exception prompting developers to modify mapper files to maintain the global uniqueness of namespace.

resultMap

The resultMap keyword abstracts the data mapping between code and persistence layer, and users need not care how to map between them.

We can use HashMap to complete the data mapping relationship between code and persistence layer. Not only is there a lack of generality, but also the code layer needs to convert objects into map structure. resultMap accepts HashMap structure and JavaBean or POJO objects, providing lightweight parameter mapping solutions.

resultMap has the function of "alias". You don't have to write a series of mapping statements for every DML. We can do this on the principle of "only defining once":

// config.xml
<typeAlias type="pojo.User" alias="User"/>

// Replace the user in resultMap with the User alias of typeAlias
<select id="query" resultType="User">
    SELECT * FROM user ORDER BY id DESC
</select>

Now if only the world was always that simple!

When POJO class member variables are not fully aligned with Table column names, mapper can be implemented by defining resultMap:

<resultMap id="user" type="User">
  <result property="userId" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

<select id="query" resultMap="user">
    SELECT * FROM user ORDER BY id DESC
</select>

'#'and'$'

As you can see from List of Codes - 1, # and ___________ and$
The mapping relationship between POJO field and DB table column name can be realized. # Replace the parameter part with? And $will be replaced by string only according to the value passed through the POJO object when sql is executed. It is easy to inject malicious code. It is not advisable! Such as:

select * from ${tableName} where name = #{name}

If tableName='user; delete user; -', the final sql statement becomes:

select * from user; delete user; -- where name = #{name}

Causes the user entire table data to be deleted.

How mybatis parses mapper files

To be continued…

test case

/**
 * Created by fujianbo on 2018/4/22.
 *
 * @author fujianbo
 * @date 2018/04/22
 */
public class TestMybatis {
    @Test
    public void testQuery() {
        SqlSession sqlSession = buildMySqlEnv("config.xml");
        List<User> userList = sqlSession.selectList("com.classfly.mapper.UserMapper.query");
        for (User p : userList) {
            System.out.println(p);
        }
    }

    @Test
    public void testInsert() {
        SqlSession sqlSession = buildMySqlEnv("config.xml");
        User user = new User();
        user.setUserId(124L);
        user.setAge(26);
        user.setPassword("test_123");
        user.setUserName("Yun Rey");
        if (sqlSession.insert("com.classfly.mapper.UserMapper.insert", user) > 0) {
            sqlSession.commit();
        }
    }

    @Test
    public void testUpdate() {
        SqlSession sqlSession = buildMySqlEnv("config.xml");
        User user = new User();
        user.setUserId(124L);
        user.setAge(26);
        user.setUserName("Yun Rey");
        user.setPassword("test_modified");
        if (sqlSession.update("com.classfly.mapper.UserMapper.update", user) > 0) {
            sqlSession.commit();
        }
    }

    @Test
    public void testDelete() {
        SqlSession sqlSession = buildMySqlEnv("config.xml");
        User user = new User();
        user.setUserId(123L);
        if (sqlSession.update("com.classfly.mapper.UserMapper.delete", user) > 0) {
            sqlSession.commit();
        }
    }

    private static SqlSession buildMySqlEnv(String resource) {
        try {
            return new SqlSessionFactoryBuilder()
                .build(org.apache.ibatis.io.Resources.getResourceAsStream(resource))
                .openSession();
        } catch (IOException e) {
            System.out.printf("Failed to build mysql environment!");
            return null;
        }
    }
}

Code link Realization

Posted by UVL on Thu, 16 May 2019 01:23:03 -0700