摘要
最近在研究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預設配置設定的庫:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL4Y2NwYzYkJmM2UmZxU2Y2UmZzE2M0EDNidzNjFDZ1EzMmZzYjRTNh9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.png)
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;
}
}
}
代碼連結 實作