1. 程式人生 > 程式設計 >一、整體認識mybatis和mybatis的體系結構

一、整體認識mybatis和mybatis的體系結構

一、myBatis核心概念


知識點

  1. 基本概念
  2. 核心物件的作用域與生命週期
  3. 介面式程式設計

1、基本概念

MyBatis 是一款優秀的持久層框架,它支援定製化 SQL、儲存過程以及高階對映。MyBatis避免了幾乎所有的JDBC程式碼和手動設定引數以及獲取結果集。MyBatis可以使用簡單的XML或註解來配置和對映原生資訊,將介面和Java的POJOs(Plain Ordinary Java Object,普通的 Java物件)對映成資料庫中的記錄。

其它持久層解決方案:JDBC、DBUtils、JdbcTemplate、Hibernate

2、核心物件的作用域與生命週期

1)新建資料庫表:user

CREATE TABLE `user` (
  `id` tinyint(4) NOT NULL AUTO_INCREMENT,`name` varchar(20) DEFAULT NULL,`create_time` datetime DEFAULT NULL,`update_time` datetime DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
複製程式碼

2)新建pojo實體類:User

public class User implements Serializable {

    private static final long serialVersionUID = -6611101295813775324L;

    private Integer id;
    private String name;
    private Date createTime;
    private Long updateTime;

    //省略setter、getter方法
}
複製程式碼

3)配置資料庫連線屬性:jdbc.properties

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
jdbc.username=root
jdbc.password=root
default.environment=dev
複製程式碼

4)配置mybatis配置檔案:mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <properties resource="jdbc.properties"></properties>

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <environments default="${default.environment}">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driverClassName}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers
        <mapper resource="mapper/UserMapper.xml"></mapper>
    </mappers>
</configuration>
複製程式碼

5)自定義型別處理器:CustomHandler

@MappedJdbcTypes(JdbcType.TIMESTAMP)
@MappedTypes(Long.class)
public class CustomHandler extends BaseTypeHandler<Long> {

    @Override
    public void setNonNullParameter(PreparedStatement ps,int i,Long parameter,JdbcType jdbcType) throws SQLException {
        //ps.setDate(i,new Date(parameter));
        ps.setTimestamp(i,new Timestamp(parameter));
    }

    @Override
    public Long getNullableResult(ResultSet rs,String columnName) throws SQLException {
        return rs.getDate(columnName).getTime();
    }

    @Override
    public Long getNullableResult(ResultSet rs,int columnIndex) throws SQLException {
        return rs.getDate(columnIndex).getTime();
    }

    @Override
    public Long getNullableResult(CallableStatement cs,int columnIndex) throws SQLException {
        return cs.getDate(columnIndex).getTime();
    }
}
複製程式碼

6)配置mapper.xml檔案:UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cyan.mapper.UserMapper">
    <resultMap id="baseResultMap" type="com.cyan.pojo.User">
        <id property="id" column="id" jdbcType="INTEGER"></id>
        <result property="name" column="name" jdbcType="VARCHAR"></result>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"></result>
        <result property="updateTime" column="update_time" jdbcType="TIMESTAMP" typeHandler="com.cyan.handler.CustomHandler"></result>
    </resultMap>

    <select id="selectUserById" parameterType="java.lang.Integer" resultMap="baseResultMap">
        select * from user where id = #{id}
    </select>
</mapper>
複製程式碼

7)編寫測試用例:UserTest

public class UserTest {

    private SqlSession session;

    @Before
    public void init()throws IOException{
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        session = sqlSessionFactory.openSession(true);
    }

    @Test
    public void test01() throws IOException {
        User result = session.selectOne("com.cyan.mapper.UserMapper.selectUserById",1);
        System.out.println(result.toString());
    }
}
複製程式碼

SqlSessionFactoryBuilder:用於構建會話工廠,基於mybatis-config.xml中的environment 、props 構建會話工廠,構建完成後即可丟棄。

SqlSessionFactory:用於生成會話的工廠,作用於整個應用執行期間,一般不需要構造多個工廠物件

SqlSession:作用於單次會話,如WEB一次請求期間,不能用作於某個對像屬性,也不能在多個執行緒間共享,因為它是執行緒不安全的。

3、介面式程式設計

由於每次呼叫時都去找對應用statement以及拼裝引數,使用上不是特別友好

myBatis引入了介面的機制,將介面與mapper.xml 的namespace名稱繫結,MyBatis就可以根據ASM工具動態構建該介面的例項。

1)建立mapper介面:UserMapper

User selectUser(Integer id);
複製程式碼

2)配置mapper.xml檔案:UserMapper.xml

<select id="selectUser" resultType="com.tuling.ssm.pojo.User">
    select * from user where id = #{id}
</select>
複製程式碼

3)編寫測試用例:UserTest

UserMapper $proxy = session.getMapper(UserMapper.class);
User user = $proxy.selectUserById(2);
System.out.println(user.toString());
複製程式碼

二、全域性的 configuration 配置


知識點

  1. properties屬性
  2. environments屬性
  3. settings屬性
  4. typeAliases屬性
  5. typeHandlers屬性
  6. mappers對映器

1、properties屬性

properties元素可以通過resource或url載入外部properties檔案中的屬性,也可以直接設定property屬性。然後在xml中就可以通過${屬性名}進行引用替換。

<properties resource="jdbc.properties"></properties>
複製程式碼

引用屬性方式

${jdbc.driverClassName}
# MyBatis3.4.2開始,支援指定預設值
${jdbc.driverClassName:com.mysql.jdbc.Driver}
複製程式碼

2、environments屬性

一個專案經常需要在例如開發壞境、測試環境、預上線環境、生產環境中等不同環境中進行部署,每個環境所對應的引數是不一樣的,myBatis 中可以通過 environment 來設定不同環境的屬性。

# 指定應用環境
default.environment=dev
複製程式碼
<environments default="${default.environment}">
    <environment id="dev">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>
    <environment id="test">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>
</environments>
複製程式碼

3、settings屬性

設定MyBatis 全域性引數,約定myBatis的全域性行為

<settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
複製程式碼

4、typeAliases屬性

在myBatis中經常會用到java中型別,如sql塊中的引數集中javaType、結果集對映中的javaType等,都要使用java 全路徑名,可以通過typeAliases屬性設定別名

<typeAliases>
    <!--<typeAlias type="com.cyan.pojo.User" alias="User"></typeAlias>-->
    <package name="com.cyan.pojo"></package>
</typeAliases>
複製程式碼

5、typeHandlers屬性

持久層框架其中比較重要的工作就是處理資料的對映轉換,把java型別轉換成jdbc型別的引數,又需要把jdbc型別的結果集轉換成java型別。在mybatis中是通過TypeHandler介面來實現的。

自定義型別處理器

public class CustomHandler extends BaseTypeHandler<Long> {

    @Override
    public void setNonNullParameter(PreparedStatement ps,int columnIndex) throws SQLException {
        return cs.getDate(columnIndex).getTime();
    }
}
複製程式碼

配置檔案方式應用

<typeHandlers>
    <typeHandler handler="com.cyan.handler.CustomHandler"
                 javaType="long" jdbcType="TIMESTAMP" />
</typeHandlers>
複製程式碼

註解方式應用

@MappedJdbcTypes(JdbcType.TIMESTAMP)
@MappedTypes(Long.class)
public class CustomHandler extends BaseTypeHandler<Long> {}
複製程式碼

6、mappers對映器

<mappers>
    <mapper resource="mapper/UserMapper.xml"></mapper>
    <mapper resource="mapper/AccountMapper.xml"></mapper>
    <!-- mapper與對應的xml檔案必須在一個包下 -->
    <!--<mapper class="com.cyan.mapper.UserMapper"/>-->
    <!--<package name="com.cyan.mapper"/>-->
</mappers>
複製程式碼

resource:基於classpath載入xml檔案

class:基於介面載入

package:掃描包下所有class,然後進行載入

約定規則

1)mapper中的namespace必須與對應的介面名稱對應

2)通過class或package載入時,xml檔案必須與介面在同一級目錄

三、mapper對映檔案


知識點

  1. sql語句塊statement
  2. 引數對映
  3. 結果集對映

1. sql語句塊statement

1)Mapper中的常見元素

resultMap – 結果集對映
select – 查詢語句
insert – 插入語句
cache – 對給定名稱空間的快取配置
parameterMap - 引數集對映
update – 更新語句
delete – 刪除語句
cache-ref - 指定快取名稱空間
sql – 可被其他語句引用的可重用語句塊。
複製程式碼

2)select中的常見屬性

id - 語句塊的唯一標識,與介面中方法名稱對應
parameterMap - 引數集對映
parameterType - 引數java型別
resultMap - 返回結果對映
resultType - 返回結果java型別
statementType - 預處理型別 
timeout - 超時時間
flushCache - 每次呼叫都會重新整理一二級快取
useCache - 是否儲存至二級快取當中去
複製程式碼

3)insert&update&delete中的常見屬性

id - 語句塊的唯一標識,與介面中方法名稱對應
parameterMap - 引數集對映
parameterType - 引數java型別
statementType - 預處理型別
timeout - 超時時間
flushCache- true每次呼叫都會重新整理一二級快取

# insert、update還具有如下三個屬性(delete則沒有)
keyProperty - 主鍵對應的java屬性,多個用 逗號分割
keyColumn - 主鍵列,多個用逗號分割
useGeneratedKeys - 插入成功後可以獲取到資料庫自動生成的主鍵值
複製程式碼

2、引數對映

引數對映是最強大功能之一,基本可以通過以下方式進行引用

1)單個簡單引數引用:如果方法中只有一個引數可通過任意名稱進行引用

User selectUserById(Integer id);

<select id="selectUserById" parameterType="java.lang.Integer" resultMap="baseResultMap">
    select * from user where id = #{id}
</select>
複製程式碼

2)多個簡單引數引用:通過引數下標引用#{param1},#{param2}

Integer insertUser(String name,Date createTime,Date updateTime);

<insert id="insertUser">
    insert into user(name,create_time,update_time) values (#{param1},#{param2},#{param3})
</insert>
複製程式碼

3)物件屬性引用:直接通過物件屬性名稱引用,巢狀物件通過.進行引用

Integer saveUser(User user);

<insert id="saveUser" parameterType="com.cyan.pojo.User"
        keyColumn="id" keyProperty="id" useGeneratedKeys="true">
    insert into user(name,update_time) values (#{name},#{createTime},#{updateTime,typeHandler=com.cyan.handler.CustomHandler})
</insert>
複製程式碼

4)map key值引用

<update id="updateUserById" parameterType="java.util.Map">
    update user set name = #{name},update_time = #{updateTime} where id = #{id}
</update>
複製程式碼

5)變數名引用

Integer modifyUserById(@Param("id") Integer id,@Param("name") String name,@Param("updateTime")Date updateTime);

<update id="modifyUserById">
    update user set name = #{name},update_time = #{updateTime} where id = #{id}
</update>
複製程式碼

6)引數拼接

基於#的引數引用,其原理是通過?佔位符進行預處理能獲得更好的效能和安全性(防止SQL注入),但有些需求是通過?佔位無法實現的,比如在一些分庫分表的場景中我們需要動態的拼接表結構。比如某系統日誌表是按年進行切割的2018_systemlog,2019_systemlog這時就可以通過如下語句進行

@Select("select * from ${year}_user where id = #{id}")
User selectUserTableByYear(String year,Integer id);
複製程式碼

3、結果集對映

1)結果集自動對映

在select中指定resultType=""後無需要任何配置 myBatis會基於resultType中的java型別及屬性自動推斷生成一個隱示的resultMap,從而完成結果對映
複製程式碼

2)resultMap

有時jdbc並不是與java Bean完全貼合這時就需要手動設定resultMap

<resultMap id="baseResultMap" type="com.cyan.pojo.User">
    <id property="id" column="id" jdbcType="INTEGER"></id>
    <result property="name" column="name" jdbcType="VARCHAR"></result>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"></result>
    <result property="updateTime" column="update_time" jdbcType="TIMESTAMP" typeHandler="com.cyan.handler.CustomHandler"></result>
</resultMap>

<select id="selectUserById" parameterType="java.lang.Integer" resultMap="baseResultMap">
    select * from user where id = #{id}
</select>

ID:用於結果集中的唯一標識
result:設定一個某通過欄位
property:java屬性名
jdbcType:jdbc型別
javaType:java型別
column:資料庫列名
typeHandler:型別處理器
複製程式碼

3)巢狀結果對映(一對一)

建立相關資料庫:

CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,`user_id` int(11) DEFAULT NULL,`money` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
複製程式碼

AccountMapper.xml:

<resultMap id="accountAndUserResultMap" type="com.cyan.pojo.Account">
    <id property="id" column="id"/>
    <association property="user" javaType="com.cyan.pojo.User">
        <id property="id" column="user_id"/>
        <result property="name" column="userName"/>
    </association>
</resultMap>

<select id="getAccountList" resultMap="accountAndUserResultMap">
    SELECT a.*,u.name userName from account a,user u where a.user_id=u.id
</select>
複製程式碼

4)引入外部select(一對一)

AccountMapper.xml:

<resultMap id="accountAmpUserResultMap" type="com.cyan.pojo.Account">
    <id property="id" column="id"/>
    <association property="user" javaType="com.cyan.pojo.User"
                 select="com.cyan.mapper.UserMapper.selectUserById" column="user_id">
    </association>
</resultMap>

<select id="selectAccountList" resultMap="accountAmpUserResultMap">
    SELECT a.*,user u where a.user_id=u.id
</select>
複製程式碼

5)巢狀結果對映(1對多)

建立相關資料庫:

CREATE TABLE teacher(
    t_id INT PRIMARY KEY AUTO_INCREMENT,t_name VARCHAR(20)
);
INSERT INTO teacher(t_name) VALUES('LS1');
INSERT INTO teacher(t_name) VALUES('LS2');

CREATE TABLE class(
    c_id INT PRIMARY KEY AUTO_INCREMENT,c_name VARCHAR(20),teacher_id INT
);
ALTER TABLE class ADD CONSTRAINT fk_teacher_id FOREIGN KEY (teacher_id) REFERENCES teacher(t_id);
INSERT INTO class(c_name,teacher_id) VALUES('bj_a',1);
INSERT INTO class(c_name,teacher_id) VALUES('bj_b',2);

CREATE TABLE student(
    s_id INT PRIMARY KEY AUTO_INCREMENT,s_name VARCHAR(20),class_id INT
);
INSERT INTO student(s_name,class_id) VALUES('xs_A',1);
INSERT INTO student(s_name,class_id) VALUES('xs_B',class_id) VALUES('xs_C',class_id) VALUES('xs_D',2);
INSERT INTO student(s_name,class_id) VALUES('xs_E',class_id) VALUES('xs_F',2);
複製程式碼

ClassesMapper.xml:

<!-- 根據 classId 查詢對應的班級資訊,包括學生,老師 -->
<!-- 方式一:巢狀結果(使用巢狀結果對映來處理重複的聯合結果的子集) -->
<resultMap id="baseResultMap1" type="com.cyan.pojo.Classes">
    <id property="id" column="c_id" />
    <id property="name" column="c_name" />
    <association property="teacher" column="teacher_id" javaType="com.cyan.pojo.Teacher">
        <id property="id" column="t_id" />
        <result property="name" column="t_name" />
    </association>
    <collection property="students" column="c_id" ofType="com.cyan.pojo.Student">
        <id property="id" column="s_id" />
        <result property="name" column="s_name" />
    </collection>
</resultMap>

<select id="getClassesById" parameterType="int" resultMap="baseResultMap1">
    select * from class c,teacher t,student s
    where c.teacher_id = t.t_id
    and c.c_id = s.class_id
    and c.c_id = #{id}
</select>
複製程式碼

6)引入外部select(一對多)

ClassesMapper.xml:

<!-- 根據 classId 查詢對應的班級資訊,老師 -->
<!-- 方式二:巢狀查詢(通過執行另外一個SQL對映語句來返回預期的複雜型別) -->
<resultMap id="baseResultMap2" type="com.cyan.pojo.Classes">
    <id property="id" column="c_id" />
    <id property="name" column="c_name" />
    <association property="teacher" column="teacher_id" javaType="com.cyan.pojo.Teacher"
        select="com.cyan.mapper.TeacherMapper.getTeacherById" />
    <collection property="students" column="c_id" ofType="com.cyan.pojo.Student"
        select="com.cyan.mapper.StudentMapper.getStudentById"/>
</resultMap>

<select id="findClassesById" parameterType="int" resultMap="baseResultMap2">
    select * from class where c_id = #{id}
</select>
複製程式碼

TeacherMapper.xml:

<resultMap id="baseResultMap" type="com.cyan.pojo.Teacher">
    <id property="id" column="t_id" />
    <result property="name" column="t_name" />
</resultMap>

<select id="getTeacherById" resultMap="baseResultMap" parameterType="int">
    select * from teacher where t_id = #{id}
</select>
複製程式碼

StudentMapper.xml:

<resultMap id="baseResultMap" type="com.cyan.pojo.Student">
    <id property="id" column="s_id" />
    <result property="name" column="s_name" />
</resultMap>

<select id="getStudentById" resultMap="baseResultMap" parameterType="int">
    select * from student where class_id = #{id}
</select>
複製程式碼