Mybatis 使用教程
参考资料 官网(http://mybatis.org/mybatis-3/zh/index.html)
示例源码地址 https://gitee.com/cq-laozhou/mybaits-source-code-analyzer
入门介绍
参考:http://mybatis.org/mybatis-3/zh/index.html
Mybatis的配置有两部分,一个是Mybatis框架本身运作起来需要的配置(主配置),通常使用mybatis-config.xml来承载,当然mybatis也提供了java的配置方式;
另外一个就是配置SQL语句的,可以用XXMapper.xml方式,也可以使用注解的方式。
mybatis-config.xml 示例,有点映像,具体配置项参考官网【http://mybatis.org/mybatis-3/zh/index.html】
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/zyy/demo/mapper/CountryMapper.xml"/>
</mappers>
</configuration>
CountryMapper.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.zyy.demo.mapper.CountryMapper">
<select id="selectAll" resultType="Country">
select id, countryname, countrycode from country
</select>
</mapper>
几个重要的类介绍:
-
SqlSessionFactoryBuilder
这个类用来解析配置的,将所有的配置最终解析到Configuration这个类里面,然后用它来创建 SqlSessionFactory,一点创建完成后,就不再需要它了。
因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 -
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,因此 SqlSessionFactory 的最佳作用域是应用作用域。 -
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
在你的所有的代码中一致地使用这种模式来保证所有数据库资源都能被正确地关闭。
- 映射器实例(XXXMapper)
映射器是一些由你创建的、绑定你映射的语句的接口。映射器接口的实例是从 SqlSession 中获得的。
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 你的应用逻辑代码
}
入门示例,见CountryMapper.xml、Country.java、 CountryMapperTest.java 。
mybatis-config.xml 主配置
参考 【http://mybatis.org/mybatis-3/zh/index.html】
Mybatis主配置文件
configuration(配置)
- properties(属性) 属性配置,主要用于支持引入外部配置文件方式。与Spring集成之后,不会使用这个。
- settings(设置) 这个比较重要,Mybatis的一些运行时行为会依赖这些配置来执行,如缓存、自动映射等。通常配置的是少数的几个。
- typeAliases(类型别名) 型别名是为 Java 类型设置一个短的名字。常见的Java类型已经内建了响应的别名,它存在的意义仅在于用来减少类完全限定名的冗余,不推荐使用。
- typeHandlers(类型处理器) 重要!!,用于处理Java类型到JDBC类型的相互转换,设置参数时从java类型转到jdbc类型,从结果集获取结果时从jdbc类型转到java类型。内置了大量的类型处理器,只有极少数特殊需求才需要自定义类型处理器。
- objectFactory(对象工厂) 使用它来创建结果对象的新实例,用默认即可。
- plugins(插件) 核心!重要,Mybatis的扩展点,允许使用插件来拦截4大方法(源码分析详细说明)
- environments(环境配置) 环境配置,与Spring集成之后,不会使用这个。
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识) 根据不同的数据库厂商执行不同的SQL语句。几乎不用。
- mappers(映射器) 定义如何找到XXXMapper文件。
XXXMapper.xml 映射文件
参考 【http://mybatis.org/mybatis-3/zh/index.html】
MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。
SQL 映射文件只有很少的几个顶级元素:
- cache – 对给定命名空间的缓存配置。
- cache-ref – 对其他命名空间缓存配置的引用。
- resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
- sql – 可被其他语句引用的可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
动态的SQL
MyBatis 采用功能强大的基于 OGNL 的表达式来至此动态SQL的拼装:
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
核心的JAVA API
SqlSessionFactory.openSession()
SqlSession.xxx() 方法。
SQL语句构建工具
SQL工具类,解决使用代码手工拼接SQL的麻烦,提高可读性,很少使用。
private String selectPersonSql() {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
FROM("PERSON P");
FROM("ACCOUNT A");
INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
WHERE("P.ID = A.ID");
WHERE("P.FIRST_NAME like ?");
OR();
WHERE("P.LAST_NAME like ?");
GROUP_BY("P.ID");
HAVING("P.LAST_NAME like ?");
OR();
HAVING("P.FIRST_NAME like ?");
ORDER_BY("P.ID");
ORDER_BY("P.FULL_NAME");
}}.toString();
}
使用DEMO
XML方式的基本用法
mapper中定义查询接口,然后在对应命名空间的xml中配置sql语句。
-
select
接口定义:
SysUser selectById(Long id);sql语句:
<select id="selectById" resultMap="userMap"> select * from sys_user where id = #{id} </select>
重点是resultMap的定义。(见com/zyy/demo/mapper/SysUserMapper.xml)
配置为resultType时,会使用自动映射(自动映射表字段到JavaBean的字段上),因此如果数据库字段和bean字段不一样时,可以使用别名来匹配。
-
insert
int insert2(SysUser sysUser);
<insert id="insert2" useGeneratedKeys="true" keyProperty="id"> insert into sys_user ( user_name, user_password, user_email, user_info, head_img, create_time) values ( #{userName}, #{userPassword}, #{userEmail}, #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP}) </insert>
int insert3(SysUser sysUser);
<insert id="insert3" > insert into sys_user ( user_name, user_password, user_email, user_info, head_img, create_time) values ( #{userName}, #{userPassword}, #{userEmail}, #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP}) <selectKey keyColumn="id" keyProperty="id" resultType="long" order="AFTER"> select last_insert_id() </selectKey> </insert>
重点是如何返回主键的值。(1. 使用jdbc提供的方法getGeneratedKeys(), 2. 单独发送一条sql查询主键值)
-
update
int updateById(SysUser sysUser);
<update id="updateById"> update sys_user set user_name = #{userName}, user_password = #{userPassword}, user_email = #{userEmail}, user_info = #{userInfo}, head_img = #{headImg, jdbcType=BLOB}, create_time = #{createTime, jdbcType=TIMESTAMP} where id = #{id} </update>
-
delete
int deleteById(Long id);
int deleteById(SysUser sysUser);<delete id="deleteById" > delete from sys_user where id = #{id} </delete>
-
接口中多个参数传递
List selectRolesByUserIdAndRoleEnabled(
@Param(“userId”) Long userId, @Param(“enabled”) Integer enabled);<select id="selectRolesByUserIdAndRoleEnabled" resultType="SysRole"> select r.id, r.role_name roleName, r.enabled, r.create_by createBy, r.create_time createTime 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 = #{userId} and r.enabled = #{enabled} </select>
接口中多个参数时,记住使用@param注解指定参数名称。
注解方式的基本用法
注解方式就是将sql语句直接写在接口上,一般情况下不建议使用。(sql和代码耦合)
-
@select
@Select({"select id, role_name roleName, enabled, create_by createBy, create_time createTime", "from sys_role", "where id = #{id}"}) SysRole selectById(Integer id);
@Results(id = "roleResultMap", value = { @Result(property = "id", column = "id", id = true), @Result(property = "roleName", column = "role_name"), @Result(property = "enabled", column = "enabled"), @Result(property = "createBy", column = "create_by"), @Result(property = "createTime", column = "createTime") }) @Select({"select id, role_name, enabled, create_by, create_time from sys_role where id = #{id}"}) SysRole selectById2(Integer id);
@ResultMap("roleResultMap") @Select("select id, role_name, enabled, create_by, create_time from sys_role") List<SysRole> selectAll();
-
@insert
@Insert({"insert into sys_role(id, role_name, enabled, create_by, create_time)", "values (#{id}, #{roleName}, #{enabled}, #{createBy}, #{createTime, jdbcType=TIMESTAMP})"}) int insert(SysRole sysRole);
@Insert({"insert into sys_role(id, role_name, enabled, create_by, create_time)", "values (#{id}, #{roleName}, #{enabled}, #{createBy}, #{createTime, jdbcType=TIMESTAMP})"}) @Options(useGeneratedKeys = true, keyProperty = "id") int insert2(SysRole sysRole);
@Insert({"insert into sys_role(id, role_name, enabled, create_by, create_time)", "values (#{id}, #{roleName}, #{enabled}, #{createBy}, #{createTime, jdbcType=TIMESTAMP})"}) @SelectKey(statement = "select last_insert_id()", keyProperty = "id", resultType = Integer.class, before = false) int insert3(SysRole SysRole);
-
@update
@Update({"update sys_role", "set role_name = #{roleName}, ", "enabled = #{enabled},", "create_by = #{createBy},", "create_time = #{createTime, jdbcType = TIMESTAMP}", "where id = #{id}"}) int updateById(SysRole sysRole);
-
@delete
@Delete({"delete from sys_role where id = #{id}"}) int deleteById(Integer id);
-
带Provider的注解(了解)
@SelectProvider @InsertProvider @UpdateProvider @DeleteProvider
@SelectProvider(type = SysPrivilegeProvider.class, method = "selectById") SysPrivilege selectById(Long id);
动态SQL
sql语句动态化(根据不同的条件,构建出不同的sql), 使用OGNL表达式来作为判断条件。
-
if
List selectByUser(SysUser sysUser);
<select id="selectByUser" resultMap="userMap"> select id, user_name, user_password, user_email, user_info, head_img, create_time from sys_user where 1=1 <if test="userName != null and userName != ''"> and user_name like concat('%', #{userName}, '%') </if> <if test="userEmail != null and userName != ''"> and user_email = #{userEmail} </if> </select>
int updateByIdSelective(SysUser sysUser);
<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> <if test="userEmail != null and userEmail != ''"> user_email = #{userEmail}, </if> id = #{id} where id = #{id} </update>
int insertSelective(SysUser sysUser);
<insert id="insertSelective" useGeneratedKeys="true" keyProperty="id"> insert into sys_user (user_name, user_password, <if test="userEmail != null and userEmail != ''"> user_email, </if> user_info, head_img, create_time ) values( #{userName}, #{userPassword}, <if test="userEmail != null and userEmail != ''"> #{userEmail}, </if> #{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP} ) </insert>
-
choose (if elseif else)
SysUser selectByIdOrUserName(SysUser sysUser);
<select id="selectByIdOrUserName" resultMap="userMap"> select id, user_name, user_password, user_email, user_info, head_img, create_time from sys_user where 1=1 <choose> <when test="id != null"> and id = #{id} </when> <when test="userName != null and userName != ''"> and user_name = #{userName} </when> <otherwise> and 1 = 2 </otherwise> </choose> </select>
-
where set trim 标签
where和set是trim的一种特例。
where标签:如果标签中的元素有返回值,则前面插入一个where,如果where后面的字符串是and或者or开始的,剔除。
set标签:如果标签中的元素有返回值,则前面插入一个set,如果set后面的字符串是","结尾的,剔除。
trim标签:prefix 添加前缀;prefixOverrides:剔除匹配的前缀字符串;suffix 添加后缀;suffixOverrides:剔除匹配后缀字符串 -
foreach
List selectByIdList(List idList);
<select id="selectByIdList" resultMap="userMap"> select id, user_name, user_password, user_email, user_info, head_img, create_time from sys_user where id IN <foreach collection="list" item="id" separator="," open="(" close=")" index="i"> #{id} </foreach> </select>
int insertList(List sysUsers);
<insert id="insertList" useGeneratedKeys="true" keyProperty="id"> insert into sys_user ( user_name, user_password, user_email, user_info, head_img, create_time) values <foreach collection="list" item="sysUser" index="i" separator=","> ( #{sysUser.userName}, #{sysUser.userPassword}, #{sysUser.userEmail}, #{sysUser.userInfo}, #{sysUser.headImg, jdbcType=BLOB}, #{sysUser.createTime, jdbcType=TIMESTAMP}) </foreach> </insert>
int updateByMap(Map<String, Object> map);
<update id="updateByMap"> update sys_user SET <foreach collection="_parameter" index="key" item="value" separator=","> ${key} = #{value} </foreach> where id = #{id} </update>
注意的是collection取值方式。
-
bind
TODO
-
多数据库支持
TODO
-
ONGL用法
TODO
高级查询
-
1v1映射
SysUser selectUserAndRoleById(Long id);
<!-- 1V1连表查询,嵌套结果映射,自动映射--> <select id="selectUserAndRoleById" resultType="com.zyy.demo.model.SysUser"> select u.id, u.user_name userName, u.user_password userPassword, u.user_email userEmail, u.user_info userInfo, u.head_img headImg, u.create_time createTime, r.id "sysRole.id", r.role_name "sysRole.roleName", r.enabled "sysRole.enabled", r.create_by "sysRole.createBy", r.create_time "sysRole.createTime" 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>
SysUser selectUserAndRoleById2(Long id);
<!--1v1 嵌套结果映射,自定义映射--> <select id="selectUserAndRoleById2" resultMap="userRoleMap"> select u.id, u.user_name, u.user_password, u.user_email, u.user_info , u.head_img , u.create_time , r.id role_id, r.role_name , r.enabled role_enabled, r.create_by role_create_by, r.create_time role_create_time 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>
<resultMap id="userRoleMap" type="com.zyy.demo.model.SysUser" extends="userMap"> <association property="sysRole" resultMap="roleMap" /> </resultMap>
注意:userRoleMap的定义,可以使用完全的自定义映射;也可以使用extends继承已经定义的resultMap来减少配置;最后推荐使用association标签来配置1v1映射。
SysUser selectUserAndRoleByIdSelect(Long id);
<select id="selectUserAndRoleByIdSelect" resultMap="userRoleMapSelect"> select u.id, u.user_name, u.user_password, u.user_email, u.user_info , u.head_img , u.create_time , ur.role_id from sys_user u inner join sys_user_role ur on u.id = ur.user_id where u.id = #{id} </select>
<resultMap id="userRoleMapSelect" type="com.zyy.demo.model.SysUser" extends="userMap"> <association property="sysRole" select="com.zyy.demo.mapper.SysRoleMapper.selectRoleById" column="{id=role_id}" fetchType="lazy"/> </resultMap>
注意:使用association实现嵌套查询(即嵌套的对象使用单独的方法-单独的sql-完成查询)
-
1对多映射
List selectAllUserAndRoles();
<resultMap id="userRoleListMap" type="com.zyy.demo.model.SysUser" extends="userMap"> <collection property="roleList" resultMap="roleMap"/> </resultMap> <select id="selectAllUserAndRoles" resultMap="userRoleListMap"> select u.id, u.user_name, u.user_password, u.user_email, u.user_info , u.head_img , u.create_time , r.id role_id, r.role_name , r.enabled role_enabled, r.create_by role_create_by, r.create_time role_create_time 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 </select>
注:1对多嵌套结果映射配置方式和 1对1嵌套结果映射非常类似(1对1是1对多特例)
特别注意:mybatis对多条结果的合并处理规则。
SysUser selectUserAndRolesSelect(Long id);
<resultMap id="userAndRolesSelectMap" type="com.zyy.demo.model.SysUser" extends="userMap"> <collection property="roleList" select="com.zyy.demo.mapper.SysRoleMapper.selectRoleByUserId" column="{userId=id}"/> </resultMap> <select id="selectUserAndRolesSelect" resultMap="userAndRolesSelectMap"> select u.id, u.user_name, u.user_password, u.user_email, u.user_info, u.head_img, u.create_time from sys_user u where u.id = #{id} </select>
以上示例为1对多的嵌套查询。
-
鉴别器映射(略)
-
枚举处理
mybatis默认使用EnumTypeHandler(将枚举转为字符串-枚举名)。mybatis提供了EnumOrdinalTypeHandler(将枚举转换为int-索引)
如果有自定义的枚举字段,这需要使用自定义类型处理,实现TypeHandler接口即可。
缓存
-
一级缓存
MyBatis 的一级缓存存在于SqlSession 的生命周期中,在同一个SqlSession 中查询
时, MyBatis会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map
对象中。如果同一个SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的
键值,当Map 缓存对象中己经存在该键值时,则会返回缓存中的对象。 -
二级缓存
使用开启缓存。
注意:当调用close 方法关闭SqlSession 时, SqlSession 才会保存查询数据到二级缓存中。在这之后二级缓存才有了缓存数据。
-
二级缓存集成ehcache,使用mybatis-ehcache扩展
-
二级缓存集成redis,使用mybatis-redis扩展
插件
-
分页插件
-
自定义插件
Spring集成
通过mybatis-spring项目来集成。
主要配置有:
1. 配置SqlSessionFactoryBean:
configLocation : mybatis主配置文件路径
datasource : 引用数据源配置
mapperLocations: *mapper.xml文件路径
typeAliasesPackage: 别名包
其他属性:查看SqlSessionFactoryBean了解。
2. 配置MapperScannerConfigurer,自动扫描注册mapper接口。
basePackage:扫描包名
annotationClass:包含了注解的接口才会被扫描进去。
Spring-boot集成
使用mybatis-spring-boot-starter项目集成。
注意,使用@Mapper注解单个接口,或者使用@MapperScan注解指定扫描包。
配置:MybatisProperties
MGB
TODO