Mybatis 从入门到入土

文章目录

  • 一、配置 MyBatis(XML 形式)
  • 二、具体语句
    • 1. Insert
      • 1.1. 只适用于支持主键自增的数据库
      • 1.2. 使用 selectKey 返回主键的值
    • 2. select
  • 三、注解方式
    • 1. @Select 注解
      • 1.1. 通过别名自动映射
      • 1.2. 使用 resultMap 方式
    • 2. @insert 注解
      • 2.1. 返回自增主键
      • 2.2. 返回非自增主键
    • 3. Provider 注解
  • 四、动态SQL
    • 1. if 用法
    • 2. choose 用法
    • 3. where set trim 用法
      • 3.1. where 用法
      • 3.2. set 用法
      • 3.3. trim 用法
    • 4. foreach 用法
    • 5. bind 用法
  • 五、高级结果映射
    • 1. 一对一映射
      • 1.1. 使用自动映射
      • 1.2. 使用 resultMap 配置一对一映射
      • 1.3. 使用 resultMap 的 association 标签配置一对一映射
    • 2. 一对多映射
    • 3. 鉴别器映射
    • 4. 枚举处理器
    • 5. 自定义处理器
  • 六、缓存
    • 1. 一级缓存
    • 2. 二级缓存
      • 2.1. 在 mapper.xml 中配置
      • 2.2. 在 Mapper 接口中配置二级缓存
      • 2.3. 使用二级缓存
      • 2.4. 二级缓存适用场景
      • 2.5. 使用 redis 实现二级缓存
  • 七、插件
    • 1. 概述
    • 2. 自定义插件

一、配置 MyBatis(XML 形式)

src/main/resources/mybatis-config.xml:



<configuration>
    
    <properties resource="jdbc.properties">properties>
    
    <settings>
        <setting name="logImpl" value="LOG4J" />
    settings>
    
    <typeAliases>
        
        
        
        <package name="tk.mybatis.simple.model"/>
    typeAliases>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="" value=""/>
            transactionManager>
            <dataSource type="UNPOOLED">
                <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>
    <mappers>
        
        <mapper resource="tk/mybatis/simple/mapper/CountryMapper.xml"/>
    mappers>
configuration>

二、具体语句

1. Insert

insert 返回主键

1.1. 只适用于支持主键自增的数据库

<insert id="insert2" useGeneratedKeys="true" keyProperty="id">
    insert into sys_user(
        user_name, user_password)
    values(
        #{userName}, #{userPassword})
insert>

1.2. 使用 selectKey 返回主键的值

<insert id="insert2" useGeneratedKeys="true" keyProperty="id">
    insert into sys_user(
        user_name, user_password)
    values(
        #{userName}, #{userPassword})
    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
        SELECT LAST_INSERT_ID()
    selectKey>
insert>

Oracle:

<selectKey keyColumn="id" result="long" keyProperty="id" order="BEFORE">
    SELECT SEQ_ID.nextval from dual
selectKey>

2. select

<select id="selectAll" resultType="tk.mybatis.simple.model.SysUser">
    select id,
        user_name userName,
        user_password userPassword
    from sys_user
select>

使用 resultType 需要设置别名来实现自动映射

三、注解方式

1. @Select 注解

1.1. 通过别名自动映射

@Select({"select id, role_name roleName ",
         "from sys_role ",
         "where id = #{id}"})
SysRole selectById(Long id);

@Select({"select id, role_name roleName 
          from sys_role 
          where id = #{id}"})
SysRole selectById(Long id);

1.2. 使用 resultMap 方式

// 定义 id,实现共用
@Results(id = "roleResultMap", value = {
    @Result(property = "id", column = "id", id = true),
    @Result(property = "roleName", column = "role_name")
})

@Results({
    @Result(property = "id", column = "id", id = true),
    @Result(property = "roleName", column = "role_name")
})
@Select("select id, role_name from sys_role where id = #{id}")
SysRole selectById2(Long id);

@ResultMap("roleResultMap")
@Select("select * from sys_role")
List<SysRole> selectAll();

2. @insert 注解

2.1. 返回自增主键

@Insert({"insert into sys_role(role_name)",
         "values(#{roleName})"})
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert2(SysRole sysRole); 

2.2. 返回非自增主键

@Insert({"insert into sys_role(role_name)",
         "values(#{roleName})"})
@SelectKey(statement = "SELECT LAST_INSERT_ID()",
           keyProperty = "id",
           resultType = Long.class,
           before = false)
int insert3(SysRole sysRole); 

3. Provider 注解

@SelectProvider(type = PrivilegeProvider.class, metod = "selectById")
SysPrivilege selectById(Long id);

PrivilegeProvider:

public class PrivilegeProvider {
    // 直接返回字符串 sql 也行
    public String selectById(final Long id) {
        result new SQL() {
            {
                SELECT("id, privilege_name, privilege_url");
                FROM("sys_privilege");
                WHERE("id = #{id}");
            }
        }.toString();
    }
}

四、动态SQL

1. if 用法

<if test="userName != null and userName != ''">
    and user_name like concat('%', #{userName}, '%')
if>

2. choose 用法

<choose>
    <when test="userName != null">
    ...
    when>
    <otherwhise>otherwhise>
choose>

3. where set trim 用法

3.1. where 用法

<where>
    <if test="userName != null and userName != ''">
        and user_name like concat('%', #{userName}, '%')
    if>
where>

3.2. set 用法

<update id="updateByIdSelective">
    update sys_user
    <set>
        <if test="userName != null and userName != ''">
            user_name = #{userName},
        if>
        <if test="userPassword != null and userPassword != ''">
            user_password = #{userPassword},
        if>
    set>
    where id = #{id}
update>

3.3. trim 用法

where 标签对应 trim 的实现:

<trim prefix="WHERE" prefixOverrides="AND |OR">
    ...
trim>

set 标签对应的 trim 实现:

<trim prefix="SET" suffixOverrides=",">
    ...
trim>
  • prefix: 给内容增加 prefix 指定的前缀
  • prefixOverrides: 把内容中匹配的前缀字符串去掉
  • suffix: 给内容增加 suffix 指定的后缀
  • suffixOverrides: 把内容中匹配的后缀字符串去掉

4. foreach 用法

where id in
<foreach collection="list" open="(" separator="," close=")" item="id" index="i">
    #{id}
foreach>

5. bind 用法

<if test="userName != null and userName != ''">
    <bind name="userNameLike" value="'%' + userName + '%'"/>
    and user_name like #{userNameLike}
if>

五、高级结果映射

1. 一对一映射

1.1. 使用自动映射

<select id="selectUserAndRoleById" resultType="tk.mybatis.simple.model.SysUser">
    select u.id, u.user_name userName, u.user_password userPassword, 
           r.id "role.id", r.role_name "role.roleName"
    from sys_user u
    inner join sys_user_role ur on u.id = ur.user_id
    inner join sys_role r on ur.role_id = r.id
    where u.id = #{id}
select>

1.2. 使用 resultMap 配置一对一映射

<resultMap id="userRoleMap" type="tk.mybatis.simple.model.SysUser">
    <result property="id" column="id"/>
    <result property="userName" column="user_name"/>
    <result property="userPassword" column="user_password"/>
    
    <result property="role.id" column="role_id"/>
    <result property="role.roleName" column="role_name"/>
resultMap>

1.3. 使用 resultMap 的 association 标签配置一对一映射

<resultMap id="userRoleMap" type="tk.mybatis.simple.model.SysUser">
    <association property="role" columnPrefix="role_" javaType="tk.mybatis.simple.model.SysRole">
        <result property="id" column="id"/>
        <result property="roleName" column="role_name"/>
    association>
resultMap>

2. 一对多映射

<resultMap id="userRoleListMap" extends="userMap" type="tk.mybatis.simple.model.SysUser">
    <id property="id" column="id"/>
    <result property="userName" column="user_name"/>
    <result property="userPassword" column="user_password"/>
    <collection property="roleList" columnPrefix="role_" javaType="tk.mybatis.simple.model.SysRole">
        <id property="id" column="id"/>
        <result property="roleName" column="role_name"/>
    collection>
resultMap>

3. 鉴别器映射

类似于 switch

<resultMap id="rolePrivilegeListMapChoose" type="tk.mybatis.simple.model.SysRole">
    <discriminator column="enabled" javaType="int">
        <case value="1" resultMap="rolePrivilegeListMapSelect"/>
        <case value="0" resultMap="roleMap"/>
    discriminator>
resultMap>

4. 枚举处理器

在 mybatis-config.xml 中添加配置:使用枚举的索引进行处理

<typeHandlers>
    <typeHandler javaType="tk.mybatis.simple.type.Enabled" 
                 handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
typeHandlers>

5. 自定义处理器

public class EnabledTypeHandler implements TypeHanlder<Enabled> {
    private final Map<Integer, Enabled> enabledMap = new HashMap<Integer, Enabled>();
    
    public EnabledTypeHandler() {
        for (Enabled enabled : Enabled.values()) {
            enabledMap.put(enabled.getValue(), enabled);
        }
    }
    
    @Override
    public void setParameter(PreparedStatement ps, int i, 
        Enabled parameter, JdbcType jdbcType) throws SQLException {

        ps.setInt(i, parameter.getValue());
    }
    
    @Override
    public Enabled getResult(ResultSet rs, int columnIndex) throws SQLException {
        Integer value = rs.getInt(columnIndex);
        return enabledMap.get(value);
    }
    
    @Override
    public Enabled getResult(CallableStatement cs, int columnIndex) throws SQLException {
        Integer value = cs.getInt(columnIndex);
        return enabledMap.get(value);
    }
    
}

在 mybatis-config.xml 中配置:

<typeHandlers>
    <typeHandler javaType="tk.mybatis.simple.type.Enabled"
                 handler="tk.mybatis.simple.type.EnabledTypeHandler"/>
typeHandlers>

六、缓存

1. 一级缓存

范围: SqlSession

概述:
MyBatis 的一级缓存存在于 SqlSession 的生命周期中,默认开启
在同一个 SqlSession 中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个 Map 对象中

清空缓存: 更新或删除操作会清空缓存,或使用 SqlSession#clearCache() 方法进行手动刷新

flushCache=“true”: 查询数据前清空当前的一级缓存

<select id="selectById" flushCache="true" resultMap="userMap">
    select * from sys_user where id = #{id}
select>

2. 二级缓存

范围: Mapper(namespace)

概述:
默认不开启
会重新创建一个对象进行 copy
无法实现分布式缓存

2.1. 在 mapper.xml 中配置

二级缓存全局开关: 不必配置,默认已为 true
mybatis-config.xml:

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

开启二级缓存:

<mapper namespace="tk.mybatis.simple.mapper.RoleMapper">
    <cache/>
mapper>

默认的二级缓存会有如下效果

  • 所有的 select 语句将会被缓存
  • 所有的 insert、update、delete 语句将会刷新缓存
  • 缓存会使用 LRU 算法来回收
  • 根据时间表(刷新间隔),缓存不会以任何时间顺序刷新
  • 缓存会存储集合或对象 1024 个引用
  • 缓存会被视为 可读可写 的,所以返回的对象不是共享的,可以安全的修改

cache 可配置的属性:

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
  • eviction:收回策略
  • LRU(最近最少使用):移除最长时间不被使用的对象,默认
  • FIFO(先进先出):按对象进入缓存的顺序来移除它们
  • SOFT(软引用):移除基于垃圾回收器状态和弱引用规则的对象
  • WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象
  • flushInterval:刷新间隔。毫秒,默认不设置(没有刷新间隔,缓存仅在调用 sql 时刷新)。
  • size:引用数目。默认 1024
  • readOnly:只读。只读的缓存会给所有调用者返回缓存对象的相同实例,不能修改。默认 false

2.2. 在 Mapper 接口中配置二级缓存

当只使用注解方式配置二级缓存时:

@CacheNamespace
public interface RoleMapper {
    ...
}

配置属性:

@CacheNamespace(
    eviction = FifoCache.class,
    flushInterval = 60000,
    size = 512,
    readWrite = true
)

当同时使用注解方式和 XML 映射文件时: 使用参照缓存
这样就会使用命名空间为 tk.mybatis.simple.mapperRoleMapper 的缓存配置,即 RoleMapper.xml 中配置的缓存

引用 Mapp 接口中配置的二级缓存

<cache-ref namespace="tk.mybatis.simple.mapper.RoleMapper"/>

2.3. 使用二级缓存

使用可读写缓存,通过序列化和反序列化来保证通过缓存获取数据时,得到的是一个 新的实例

sqlSession = getSqlSession();
try {
    // 获取 RoleMapper 接口
    RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
    // 调用 selectById 方法,查询 id = 1 的用户
    SysRole role2 = roleMapper.selectById(1L);
    // 第二个 session 获取的用户名是 New Nam
    Assert.assertEquals("New Name", role2.getRoleName());
    // 这里的 role2 和前一个 session 查询的结果是两个不同的实例
    Assert.assertNotEquals(role1, role2);
    // 获取 role3
    SysRole role3 = roleMapper.selectById(1L);
    // 这里的 role2 和 role3 是两个不同的实例
    Assert.assertNotEquals(role2, role3);
} finally {
    // 关闭 sqlSession
    sqlSession.close();
}

2.4. 二级缓存适用场景

  • 以查询为主的应用中,只有尽可能少的 curd
  • 绝大多数以单表操作存在时,由于很少存在互相关联的情况,因此不会出现脏数据
  • 分布式场景不能使用,需要借助第三方缓存中间件

2.5. 使用 redis 实现二级缓存

  1. 加入依赖

     <dependency>
         <groupId>org.mybatis.cachesgroupId>
         <artifactId>mybatis-redisartifactId>
         <version>1.0.0-beta2version>
     dependency>
    
  2. 使用缓存

    @CacheNamespace(implementation = RedisCache.class)
    public interface IUserMapper {
        ...
    }
    

七、插件

1. 概述

对 mybatis 的扩展,其原理是拦截器,在四大组件(ExecutorStatementHandlerParamterHandlerResultSetHandler)使用时拦截处理

Mybatis 从入门到入土_第1张图片

允许拦截的方法:

  • Executor(执行器):update、query、commit、rollback
  • StatementHandler(SQL 语句构建器):prepare、parameterize、batch、update、query
  • ParameterHandler(参数处理器):getParameterObject、setParameters
  • ResultSetHandler(结果集处理器):handleResultSets、handleOutputParameters

原理:
在四大组件对象创建时遍历所有的拦截器,每个拦截器都使用 interceptor.plugin(target) 方法对目标类进行增强

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object object, BoundSql sql, InterceptorChain interceptorChain) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql); 
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); 
    return parameterHandler;
}
public Object pluginAll(Object target) { 
    for (Interceptor interceptor : interceptors) { 
        target = interceptor.plugin(target);
    } 
    return target; 
}

2. 自定义插件

  1. 实现 Interceptor 并添加注解
    @Intercepts({
        @Signature(
            // 指定组件
            type = Executor.class,
            // 指定方法
            method = "query",
            // 指定参数确定方法
            args = {MapperStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
    })
    public class ExamplePlugin implements Interceptor {
        // 每次操作都会进入该方法
        @Override 
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("对方法进行了增强...."); 
            return invocation.proceed();
        }
        
        // 把这个拦截器生成一个代理放到拦截器链中
        @Override 
        public Object plugin(Object target) { 
            System.out.println("将要包装的目标对象" + target);
            return Plugin.wrap(target, this);
        }
        
        // 插件初始化时调用一次,获取配置的属性,如下:
        @Override
        public void setProperties(Properties properties) { 
            System.out.println("初始化参数" + properties);
        }
    }
    
  2. 配置到配置文件中
    <plugins> 
        <plugin interceptor="com.xxx.plugin.ExamplePlugin">
            <property name="name" value="Bob"/>
        plugin>
    plugins>
    

你可能感兴趣的:(Mybatis 从入门到入土)