一、配置 MyBatis(XML 形式)
src/main/resources/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文件,使用 ${jdbc.driver} 等形式注入-->
<properties resource="jdbc.properties"></properties>
<settings>
<setting name="logImpl" value="LOG4J" />
</settings>
<typeAliases>
<!--给单独的实体起别名-->
<!-- <typeAlias type="com.lagou.pojo.User" alias="user"></typeAlias>-->
<!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
<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}"/>
<!--<property name="driver" value="com.mysql.jdbc.Driver"/>-->
<!--<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>-->
<!--<property name="username" value="root"/>-->
<!--<property name="password" value=""/>-->
</dataSource>
</environment>
</environments>
<mappers>
<!-- <package name="tk.mybatis.simple.mapper"/> -->
<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 实现二级缓存
-
加入依赖
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency>
-
使用缓存
@CacheNamespace(implementation = RedisCache.class) public interface IUserMapper { ... }
七、插件
1. 概述
对 mybatis 的扩展,其原理是拦截器,在四大组件(Executor
、StatementHandler
、ParamterHandler
、ResultSetHandler
)使用时拦截处理
允许拦截的方法:
- 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. 自定义插件
- 实现 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); } // 插件初始化时调用一次,获取配置的属性,如下:<name, Bob> @Override public void setProperties(Properties properties) { System.out.println("初始化参数" + properties); } }
- 配置到配置文件中
<plugins> <plugin interceptor="com.xxx.plugin.ExamplePlugin"> <property name="name" value="Bob"/> </plugin> </plugins>