GitHub 学习路径(含笔记与代码)

引言

image.png

1. 安装

  1. <dependency>
  2. <groupId>org.mybatis</groupId>
  3. <artifactId>mybatis</artifactId>
  4. <version>3.4.5</version>
  5. </dependency>

2. 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">

2.1 Configuration 配置

注意:配置过程中有顺序要求,否则报错

2.1.1 properties(属性)

  • 使用一:maven 工程 resource 路径下(若路径下存在此配置文件,则无需填充 property 属性)
    <properties resource="jdbcConfig.properties">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/cyt_mybatis"/>
    <property name="username" value="root"/>
    <property name="password" value="1234"/>
    </properties>
    

  • 使用二:url 路径【协议 主机 端口 URI】(以下为 maven 文件对应的 url 路径)
    <properties 
    url="file:///E:/JavaLearing/code/testMybatis/src/main/resources/jdbcConfig.properties">
    </properties>
    

2.1.2 settings(设置)

  • 使用一:设置打印 SQL 语句,方便查错
  • 使用二:支持二级缓存
  • 使用三:支持延迟加载
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="true"/>
    

2.1.3 typeAliases(类型别名)

  - **使用一:**指定配置别名的实体类,指定别名后不区分大小写
  - <typeAliases>  <typeAlias type="com.cyt.domain.User" alias="user"</typeAlias>  </typeAliases> 
  - **使用二**:指定配置别名的包,该包下的实体类均会注册别名,类名就是别名,指定别名后不区分大小写
  - <typeAliases>  <package name="com.cyt.domain"/>  </typeAliases>

2.1.4 environments(环境配置)

<environments default="mysql">    
    <environment id="mysql">        
        <transactionManager type="JDBC">
             <property name="closeConnection" value="false"/>【选填,用于阻止默认关闭行为】
        </transactionManager>        
        <dataSource type="POOLED">            
              <!-- 其中 ${xxx} 必须与配置文件 jdbcConfig.properties 中的 key 保持一致 --> 
              <property name="driver" value="${jdbc.driver}"/>          
              <property name="url" value="${jdbc.url}"/>         
              <property name="username" value="${jdbc.username}"/>           
              <property name="password" value="${jdbc.password}"/>       
         </dataSource>    
    </environment>
</environments>
  • 补充:
    • 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境,即一种类型的数据库
    • Spring + MyBatis 无需配置事务管理器, Spring 模块会使用自带的管理器来覆盖前面的配置
    • dataSource
      • 为了使用延迟加载,dataSource 是必须配置的
      • 三种内建的数据源类型 type=”[ UNPOOLED | POOLED | JNDI ]
        • UNPOOLED | POOLED 涉及连接池的概念
        • JNDI 用于 EJB 或应用服务器这类容器使用,可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用
    • 报错记录: property name 的顺序和内容必须和 jdbcConfig.properties 中一致,否则会报错 BuilderException:Error parsing SQL Mapper Configuration.. unknown DataSource property

      2.1.5 mappers(映射器)

<mappers> 
    1. 单个 mapper 映射
     1.1。 类路径的资源引用: <mapper resource="com/cyt/dao/IUserDao.xml"/>
      1.2. 映射器接口实现类的完全限定类名: <mapper class="com.cyt.dao.IUserDao"/>
    1.3. 使用完全限定资源定位符(URL):
    <mapper url="file:///E:/JavaLearing/code/testMybatis/src/main/resources/.com/cyt/dao/IUserDao.xml"/>
    2. 多个 mapper,将包内的映射器接口实现全部注册为映射器
    <package name="org.mybatis.builder"/>
</mappers>

2.1.6 其他

  • typeHandlers:将 预处理语句设置参数或从结果集取值 以合适类型转换成 java 类型;


    • or

    • 特殊_枚举处理器(处理任何继承 Enum 的类):

      • 补充:支持重写,需实现 org.apache.ibatis.type.TypeHandler 接口 or 继承 org.apache.ibatis.type.BaseTypeHandler 类
  • objectFactory(对象工厂)
  • plugins(插件):实现 Interceptor 接口,并指定想要拦截的方法签名
  • databaseIdProvider(数据库厂商标识)

    3. 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.cyt.dao.IUserDao">
      <resultMap/>
      <select/> 
      <sql/> 等
    </mapper>
    

3.1 基础

3.1.1 sql 标签

  • 定义公共代码段 : select * from user

    3.1.2 resultMap

    见多表查询

    3.1.3 CURD 语句

  • select 模糊查询:

    <select id="findByName" parameterType="string" resultType="com.cyt.domain.User">
    select * from user where username like '%${value}%'
    </select>
    
  • select 包含实体类的条件类查询:

    <!-- 根据 queryVo 的条件查询用户 -->
    <select id="findUserByVo" parameterType="com.cyt.domain.QueryVo" resultType="com.cyt.domain.User"> 
    select * from user where username like #{user.username}
    </select>
    
  • select 返回数字:

    <select id="findTotal" resultType="java.lang.Integer">
    select count(*) from user;
    </select>
    
  • insert

    <insert id="saveUser" parameterType="com.cyt.domain.User">  
     <!-- 配置插入操作后,获取插入数据的 id ,其中keyProperty对应的是实体类中的名称-->    
    <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
        select last_insert_id();
    </selectKey>   
    <!-- 字段的顺序必须和 User 类中的类成员变量顺序一致,且 #()取值与 getXXX 一致-->
    insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
    </insert>
    //自增主键
    <insert id="saveUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
    
  • delete

    <delete id="deleteUser" parameterType="java.lang.Integer">
    delete from user where id=#{user_id};
    </delete>
    
  • update

    <update id="updateUser" parameterType="com.cyt.domain.User">    
    update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
    </update>
    

3.2 动态 SQL

3.2.1 foreach & if

<!--根据queryvo中提供的id集合实现查询用户列表-->
<select id="findUserInIds" parameterType="QueryVo" resultType="user">    
    <include refid="defaultUser"></include>   【对 <sql/> 的引用】
    <where>       
         <if test="ids != null or ids.size() > 0">            
        <foreach collection="ids" open="id in (" close=")" item="uid" separator=",">${uid} </foreach>        
        </if>    
    </where>
</select>

3.2.2 set & trim

<update id="testSetLabel" parameterType="user">   
 update user
    <trim prefix="set" suffix="where id=#{id}" suffixOverrides=",">        
        <if test="address != null and address != ''">address = #{address}, </if>        
        <if test="username != null and username != ''"> username = #{username}, </if>   
    </trim>
</update>

3.2.3 choose & when(等同于 switch case)

<select id="testChooseLabel" parameterType="user" resultType="User">    
    <include refid="defaultUser"></include>    
    <where>        
        <choose>            
            <when test="username != null and username != ''"> and username = #{username} </when>
            <when test="sex != null and sex != ''">and sex = #{sex} </when>
            <otherwise>  and address = #{address} </otherwise>
        </choose>
    </where>
</select>

3.2.4 map

  • 使用一:

    <select id="testMapLabel" parameterType="java.util.Map">
    insert into user
      <foreach collection="userMaps.keys" item="key" open="(" close=")" separator=",">${key}</foreach>                values
      <foreach collection="userMaps.values" item="value" open="(" close=")" separator=",">#{value}</foreach>
    </select>
    
  • 使用二:

    <select id="testMapLabel" parameterType="java.util.Map">
    insert into user
      <foreach collection="userMaps" item="key" index="key1" open="(" close=")" separator=",">${key1</foreach>
    values
      <foreach collection="userMaps" item="value" index="value1" open="(" close=")" separator=",">#{value}</foreach>
    </select>
    
  • 补充: foreach 标签的 collection 属性默认值为:_parameter,非默认情况下,应与 dao 接口的方法保持一致,eg. void testMapLabel(@**Param(value = “userMaps”) **Map userMap)-

    3.2.5 mybatis 动态 sql 支持

    注意:mybatis 3.2.x 版本之后新增** **LanguageDriver 接口

  • 使用配置:

    • sqlMapConfig.xml : mymyLanguage 是 LanguageDriver 的实现类

      方式一:
      <typeAliases>
      <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
      </typeAliases> 
      方式二:
      <settings>
      <setting name="defaultScriptingLanguage" value="myLanguage"/>
      </settings>
      
    • IUserDao.xml

      <select id="findAll" lang="myLanguage">
      select * from user;
      </select>
      

      3.3 多表查询

  • resultMap

    • association 的两种方法【第二种方法更为简洁,推荐使用】
      • 要求:实体类中必须有和关联实体类相关的属性;
      • property 对应实体类属性名,column 对应最终查询出的表结构的命名;
      • ;
      • 必须指定 javaType: 表示关联类的全限定类名,此处表示方式是因为 sqlMapConfig 有别名设置;
    • 实现一: ```java

      对应的 sql 语句:


- 实现二:
```java
    <resultMap id="accountUserMap" type="account">
        <id property="id" column="id"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!--延迟缓存-->
        <association property="user" column="uid" javaType="user" select="com.cyt.dao.IUserDao.findUserById">
        <!-- 注意要在 com/cyt/dao/IUserDao.xml 中有 findUserById 的 sql 语句 -->
        </association>
    </resultMap>

    对应的 sql 语句:
    1.IAccountDao.xml 
    <select id="findAll" resultMap="accountUserMap">
        select * from account;
    </select>
    2.IUserDao.xml 
    <select id="findUserById" parameterType="java.lang.Integer" resultType="com.cyt.domain.User" useCache="true">
        select * from user where id=#{id}
    </select>
  • collection 同有两种方法,此处略

注意: ofType,不写或写成其他的会报错:空指针异常

    <resultMap id="roleMap2" type="role">
        <!--column 值必须与sql语句生成的表的字段名一致-->
        <id property="id" column="rid"></id>
        <result property="roleName" column="rname"></result>
        <result property="roleDesc" column="rdesc"></result>
        <collection property="users" ofType="user">
            <id property="id" column="id"></id>
            <result property="username" column="username"></result>
            <result property="address" column="address"></result>
            <result property="sex" column="sex"></result>
            <result property="birthday" column="birthday"></result>
        </collection>
    </resultMap>

    对应的 sql 语句
    <!-- 多对多映射 获取角色所对应的用户 -->
    <select id="findRoleUsers" resultMap="roleMap2">
        select u.*,r.id as rid,r.role_name as rname,r.role_desc as rdesc from role r
            left outer join user_role ur on ur.rid=r.id
            left outer join user u on u.id=ur.uid;
    </select>

4. Annotation 映射

4.1 动态 SQL 注解

@select、@update、@insert、@delete (以下 test 支持动态 sql)

  • @Select(“SELECT * FROM users WHERE id IN (#{userIdList})”)

@Lang(SimpleSelectInLangDriver.class)
List selectUsersByUserId(List userIdList);

  • @Update(“UPDATE users (#{user}) WHERE id = #{id}”)

    @Lang(SimpleUpdateLangDriver.class)
    void updateUsersById(User user);

  • @Insert(“INSERT INTO users (#{user})”)

    @Lang(SimpleInsertLangDriver.class)
    void insertUserDAO(User user);

  • @Delete(“DELETE FROM user WHERE id=#{user_id}”)

    @Lang(SimpleDeleteLangDriver.class)
    void deleteUsersById(User user);

  • 举例 : SimpleSelectInLangDriver 实现类

public class SimpleSelectInLangDriver extends XMLLanguageDriver implements LanguageDriver{ }

4.2 建立实体类与数据库的映射

  • 注解实现 resultMap

      @Select("select * from user")
      @Results(id="userMap",
              value = {
              @Result(id=true, column = "id", property = "userId"),
              @Result(id=true, column = "username", property = "userName"),
              @Result(id=true, column = "address", property = "userAddress"),
              @Result(id=true, column = "sex", property = "userSex"),
              @Result(id=true, column = "birthday", property = "userBirthday")
      })
      List<User1> findAll();
    
      @Select("select * from user where username like '%${value}%'")
      @ResultMap(value = "userMap")
      List<User1> findByName(String name);
    

4.3 多表查询

  • 一对一

      1. IAccountDao.class
      @Results(id="accountMap",
              value = {
             @Result(id=true,property = "id",column = "id"),
             @Result(property = "uid",column = "uid"),
             @Result(property = "money",column = "money"),
             @Result(property = "user",column = "uid",one = @One(select = "com.cyt.dao.IUserDao_annotation.findUserById",fetchType = FetchType.EAGER))
             //one 表示一对一的映射关系,fetchType.eager 是及时加载
              })
      @Select("select * from account")
      List<Account> findAll();
    
      2. IUserDao.class
      @Select("select * from user where id=#{id}")
      @ResultMap("userMap")
      User findUserById(Integer id);
    
  • 一对多

      1. IUserDao.class
      @Select("select * from user")
      @Results(value = {
              @Result(id=true, column = "id", property = "id"),
              @Result(column = "username", property = "username"),
              @Result(column = "address", property = "address"),
              @Result(column = "sex", property = "sex"),
              @Result(column = "birthday", property = "birthday"),
              @Result(property = "accounts", column = "id", many = @Many(select = "com.cyt.dao.IAccountDao_annotation.findAccountById",fetchType = FetchType.LAZY))
              })
      List<User2> findAllUserAccount();
      2. IAccountDao.class
      @Select("select * from account where uid=#{uid}")
      @Results(value = {
                      @Result(id=true,property = "id",column = "id"),
                      @Result(property = "uid",column = "uid"),
                      @Result(property = "money",column = "money")})
      List<Account> findAccountById(Integer uid);
    

5. 执行 & 过程分析

5.1 执行过程

(1)读取配置文件
in = Resources.getResourceAsStream(“SqlMapConfig_CURD.xml”);
(2)创建 SqlSessionFactory 工厂 【创建者模式:细节隐藏,直接调用】
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
(3)使用工厂创建 sqlSession 对象【工厂模式:解耦】
sqlSession = factory.openSession(true); // 设置自动提交,否则需手动补充 sqlSession.commit()
(4)获取 Dao 类【代理模式:不修改源码,对已有方法 selectList 增强】
userDao = sqlSession.getMapper(IUserDao.class); // 代理 Dao
List users = session.selectList(“com.cyt.dao.IUserDao.findAll”);// 实例 Dao IUserDaoImpl
(5)执行 Dao 中的方法
List users = userDao.findByName(“王”);
(6)释放资源
sqlSession.close(); in.close();

5.2 自定义Mybatis(理解源码)

  • 第一步:
    • Resource 读取指定路径下的 xml 文件,获取 InputStream 对象,即 config;
  • 第二步:
    • SqlSessionFactoryBuilder 调用 XMLConfigBuilder.loadConfiguration(config) 解析 xml 文件,获取 jdbcConfig 和 mapper(调用 loadMapperConfiguration 或 loadMapperAnnotation ),并包装成一个对象 cfg
  • 第三步:
    • DefaultSqlSessionFactory 传递 cfg,并调用 DefaultSqlSession() 方法;
  • 第四步:

    • DefaultSqlSession 构造函数中调用 dataUtil ,利用 cfg 配置信息注册数据驱动,获取数据连接 conn;
      public static Connection getConnection(Configuration cfg){
       Class.forName(cfg.getDriver());       
      return DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword()); 
      }
      
  • 第五步:DefaultSqlSession getMapper 方法实现代理 dao;

    userDao = sqlSession.getMapper(IUserDao.class);
    ————————————————————————————————————————————————————————————————
    以下为自定义代码,理解 mybatis 的执行过程
    ————————————————————————————————————————————————————————————————
    ——————————————————————————5.1
    public <T> T getMapper(Class<T> daoInterfaceClass) {
      return (T)Proxy.newProxyInstance(
              daoInterfaceClass.getClassLoader(),  
              new Class[]{daoInterfaceClass},
                new MapperProxy(cfg.getMappers(), connection));    // 不指定返回类型的话,会报错
    }
    ——————————————————————————5.2
    class MapperProxy{
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          //1. 获取方法名    
          String methodName = method.getName();    
          //2. 获取方法所在的类名称    
          String className = method.getDeclaringClass().getName();   
          //3. 组合 key    
          String key = className + "." + methodName;   
          //4. 获取 mappers 中的 Mapper 对象    
          Mapper mapper = mappers.get(key);   
           //5. 判断是否有 mapper    
          if (mapper == null) {
                  throw new IllegalArgumentException("传入的参数有误");    }
          //6. 调用工具类执行查询所有    
          return new Executor().selectList(mapper, conn);
       }
    }
    ——————————————————————————5.3
    public class Executor {
      public <E> List<E> selectList(Mapper mapper, Connection conn) {
           String queryString = mapper.getQueryString(); //select * from user 
           String resultType = mapper.getResultType();// com.cyt.domain.User           
           Class domainClass = Class.forName(resultType); 【解释之前 dao 类为什么实现 serialiable 接口】
           //2.获取PreparedStatement对象           
           pstm = conn.prepareStatement(queryString);           
           //3.执行SQL语句,获取结果集            
           rs = pstm.executeQuery();           
           //4.封装结果集  略         
             return list;      
       }
    }
    

    分析:映射文件是个大map,key 是 全限定类名,value 是 mapper,mapper 由 sql 语句 和 返回类型 两部分组成,以下是整个过程的图示;
    自定义Mybatis分析.png

    5.3 补充

    • builder、factory、session 的最佳作用域
      • SqlSessionFactoryBuilder 方法作用域,一旦创建 SqlSessionFactory 便不再需要;
      • SqlSessionFactory 应用作用域,如使用单例模式或者静态单例模式,鼓励重用;
      • SqlSession 请求或方法作用域,与线程对应,非线程安全,不被共享,确保 sqlSession 用完即关闭,不能用于一个类的静态域,类的实例变量或将 SqlSession 实例引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession;
    • 自定义注解(eg:注解 sql 查询)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
_ _String value();
}

6. mybatis 缓存

6.1 一级缓存

  • MyBatis 默认开启一级缓存,无需手动配置;
  • 一级缓存是基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache就将清空;
  • MyBatis 会在一次会话中,一个 SqlSession 对象中创建一个本地缓存(local cache),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户;

mybatis 学习 - 图3

6.2 二级缓存

  • resultMap 对应的实体类必须要序列化,不然会报 java.io.NotSerializableException;
  • 与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),也是一个内存级别的缓存;
  • 默认的二级缓存功能如下:

1.映射语句文件中所有的select语句将会被缓存;
2.映射语句文件中所有的insert update delete语句会刷新缓存;
3.缓存会使用(Least Flush Interval,LRU最近最少使用的)算法来收回;
4.根据时间表(如 no Flush Interval,没有刷新间隔),缓存不会以任何时间顺序来刷新;
5.缓存会存储集合或对象(无论查询方法返回什么类型的值)的1024个引用;
6.缓存会被视为read/wriete(可读/可写)的,意味着对象检索不是共享的,而且可以安全的被调用者修改,而不干扰其他调用者或者线程所做的潜在修改;

  • mybatis 自带的二级缓存
    • 存储于 sqlFactory 中,同一 factory 建立的多个 sqlSession 共享内存;
    • xml 配置:
      • mybatis 框架( SqlMapConfig.xml );
      • 映射文件(IUserDao.xml mapper );
      • 让当前的操作支持二级缓存