天天看點

極簡Mybatis之旅(一):CRUD

摘要

最近在研究mybatis架構,作為一個優秀ORM架構,mybatis很多優秀的設計思想值得借鑒。

mybatis的配置檔案裡,主要是config和mapper。config定義了全局參數:資料源類型(POOL, UNPOOLED, JNDI)、事務管理類型(預設為managed)、庫url、賬戶資訊和mapper檔案路徑。

config配置

// 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',是mybatis預設配置設定的庫:

極簡Mybatis之旅(一):CRUD

mapper配置

首先我們要問自己以下三個問題:

  • 為什麼要配置mapper檔案?
  • 怎麼配置mapper檔案?
  • mybatis如何解析mapper檔案?

mapper檔案的作用

mapper檔案提供一種持久化層與應用層的"通信協定",通過mysql關鍵字resultMap, parameterType等維護應用代碼DO對象與持久化存儲資料之間的關聯關系。

mapper檔案裡定義了select, insert, update, delete四種常用的DML語句,并定義statement={mapper namespace}.{operation}實作在應用代碼層執行調用DB操作。

配置mapper檔案

這裡先給出測試用例用到的mapper配置檔案的關鍵部分:

<!-- 代碼清單-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>           

mapper namespace定義了DML語句的作用範圍,那如果兩個不同的mapper檔案定義相同的namespace會發生什麼?由于mysql保持namespace的全局唯一性,是以在解析mapper檔案時mysql會抛異常提示開發者修改mapper檔案以保持namespace的全局唯一性。

resultMap

resultMap關鍵字将代碼和持久化層的資料映射抽象出來,使用者無需關心兩者之間如何映射。

我們可以使用HashMap完成代碼和持久化層的資料映射關系,不但缺乏通用型且代碼層需要将對象轉換成map結構。resultMap接受HashMap結構和JavaBean或者POJO對象,提供輕量級的參數映射方案。

resultMap有"别名"的功能,你無需每個DML都寫一大串的映射語句,秉着"僅定義一次"的原則,我們可以這麼做:

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

// 用typeAlias的User别名替換了resultMap中的user
<select id="query" resultType="User">
    SELECT * FROM user ORDER BY id DESC
</select>           

Now if only the world was always that simple!

當POJO類成員變量并未完全和Table列名"對齊"時,可通過定義resultMap的方式來實作mapper:

<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>           

'#'和'$'

從"代碼清單-1"裡可以看出,

#和$

均可實作POJO字段和DB表列名的映射關系。

#

将參數部分用

?

替換,而

$

僅根據執行sql時通過POJO對象透傳的值進行字元串替換,容易被注入惡意代碼,不可取!如:

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

如果tableName='user; delete user; --',最終sql語句變為:

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

導緻user整張表資料被删除。

mybatis如何解析mapper檔案

To be continued...

測試用例

/**
 * 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("芸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("芸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;
        }
    }
}           
代碼連結

實作