MyBatis
iBatis -> apache MyBatis
使用步骤:
- 添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
- 添加配置文件 mybatis-config.xml
用这个配置文件来告诉 mybatis 如何连接数据库
告诉 mybatis 到哪里去找映射关系
.....
<mappers>
<!-- 指定映射文件的位置 -->
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
- 提供一个 映射文件
用来管理 sql 语句,描述 sql 语句与数据库表之间的映射关系
<?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="命名空间">
<insert id="sql的名字" parameterType="参数类型" useGeneratedKeys="true" keyProperty="主键对应的属性">
<update id="sql的名字" parameterType="参数类型">
<delete id="sql的名字" parameterType="参数类型">
<select id="sql的名字" parameterType="参数类型" resultType="结果类型,如果是集合是集合中元素的类型">
<if test="条件"> sql 片段 </if>
<set></set>
</mapper>
参数类型:
int, string, #{名字任意}
map #{key}
自定义javabean #{属性名}
- 调用 mybatis api 使用映射文件真正执行增删改查
InputStream in = Resources.getResourceAsStream("配置文件的路径");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = factory.openSession();
sqlSession
.insert("命名空间+sql的名字", sql需要的参数对象)
.update("命名空间+sql的名字", sql需要的参数对象)
.delete("命名空间+sql的名字", sql需要的参数对象)
.selectOne("命名空间+sql的名字", sql需要的参数对象) 结果只有一个时
.selectList("命名空间+sql的名字", sql需要的参数对象) 结果是list集合
对增删改
sqlSession.commit();
关闭
sqlSession.close();
3. Mybatis 映射文件(重点)
新增
<!-- #{sname} 用来获取 Student 参数对象中的 sname属性-->
<!-- useGeneratedKeys="true" 是告诉 mybatis 要使用由数据库产生的主键值 -->
<!-- keyProperty="主键对应的属性名" -->
<insert id="abc" parameterType="domain.Student"
useGeneratedKeys="true" keyProperty="sid">
insert into student(sid, sname, birthday, sex)
values ( null, #{name}, #{birthday}, #{sex})
</insert>
删除
<delete id="delete" parameterType="int">
delete from student where sid = #{sid}
</delete>
查询所有
<select id="findAll" resultType="domain.Student">
select sid,sname name,birthday,sex from student
</select>
根据id查询
<select id="findById" resultType="domain.Student" parameterType="int">
select sid,sname name,birthday,sex from student where sid = #{sid}
</select>
更新
<update id="update" parameterType="domain.Student">
update student set sname=#{name}, birthday=#{birthday}, sex=#{sex} where sid=#{sid}
</update>
<!-- 动态更新列
Student stu = new Student();
stu.setSid(1001);
stu.setSex("男");
update student set sex=#{sex} where sid=#{sid}
用 set 标签可以去除多余的逗号
-->
<update id="update" parameterType="domain.Student">
update student
<set>
<if test="name != null">
sname=#{name},
</if>
<if test="birthday != null">
birthday=#{birthday},
</if>
<if test="sex != null">
sex=#{sex},
</if>
</set>
where sid=#{sid}
</update>
分页查询
<!-- m , n
java.util.Map -> map
java.util.List -> list
java.lang.String -> string
map.put("m", 0);
map.put("n", 5);
-->
<select id="findByPage" parameterType="map" resultType="domain.Student">
select sid,sname name,birthday,sex from student limit #{m}, #{n}
</select>
动态 sql 生成
- foreach 循环
delete from 表 where id in(1008,1011,1012);
<!-- list (1008,1011,1012, 1013) -->
<delete id="" parameterType="list">
delete from 表 where id in
<foreach collection="list" item="x" open="(" close=")" separator=",">#{x}</foreach>
</delete>
- if where 生成动态sql
<select id="findByCondition" parameterType="map" resultType="domain.Student">
select sid,sname name,birthday,sex from student
<where> <!-- 可以用 where 去除多余的 and -->
<if test="sname!=null">
and sname = #{sname}
</if>
<if test="birthday!=null">
and birthday = #{birthday}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
</where>
</select>
-
对于大于小于条件的处理
方法1: 对每个特殊字符转转义,例如 < – <
方法2: <![CDATA[ 内容 ]]> -
#{ } 与 ${ } 的区别
- #{ } 底层是替换为 ?, 只能占位值
- ${ } 底层是直接拼接字符串, 有注入攻击问题
- 尽量使用 #{ } , 只有在某些功能不能用 #{ } 实现时(例如排序)采用 ${ }
4. 通过日志工具监控mybatis生成的sql语句
logback
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
添加一个 logback.xml 到 resources文件夹
<!-- 用来控制查看那个类的日志内容(对mybatis name 代表命名空间) -->
<logger name="mapper.StudentMapper" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</logger>
5. Mapper 接口
在mybatis-config.xml中添加映射关系
<mappers>
<!-- 指定映射接口的 包名.类名 -->
<mapper class="mapper.StudentMapper"/>
</mappers>
@Insert @Option
@Update
@Delete
@Select
src/main/java目录下新建mapper目录将所有的mapper放在该目录下。
所有的mapper都为接口类。
public interface StudentMapper {
@Insert("insert into student(sid,sname,birthday,sex) values(null,#{sname}, #{birthday}, #{sex})")
@Options(useGeneratedKeys = true, keyProperty = "sid")
void insert(Student student);
@Update("update student set sname=#{sname}, birthday=#{birthday}, sex=#{sex} where sid=#{sid}")
void update(Student student);
@Delete("delete from student where sid=#{sid}")
void delete(int sid);
@Select("select * from student")
List<Student> findAll();
@Select("select * from student where sid = #{sid}")
Student findById(int sid);
@Select("select * from student limit #{m}, #{n}")
List<Student> findByPage(Map map);
// 同时根据姓名和性别查询
@Select("select * from student where sname=#{a} and sex=#{b}")
List<Student> find1(@Param("a") String sname, @Param("b") String sex);
@Select("select * from student where sname=#{a} and sex=#{b}")
List<Student> find2(Map<String, Object> map);
void deleteByIds(List<Integer> ids);
}
原理
// 这个类是使用了 jdk的动态代理技术 在代码运行期间生成的类
public class $Proxy10 implements StudentMapper{
private SqlSession sqlSession;
public $Proxy10(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
// 其中 sql语句从@Insert注解获得, 参数对象就是student
public void insert(Student student) {
sqlSession.insert(sql, 参数对象)
}
// 其中 sql语句从@Select注解获得
public List<Student> findAll() {
return sqlSession.selectList(sql);
}
}
3. Mapper接口不足
1. 接口方法不能直接应用多个方法参数
解决方法:
- 用map传递多个参数, 每个参数对应map中的一个键值对
- 用@Param注解(上面例子中姓名与性别同时查询)
2. Mapper 接口中不能有方法的重载
定义方法是不要方法名冲突
异常信息:
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for 接口名.方法名(重复的)
3. 使用Mapper接口方式实现动态sql比较复杂
方法1: 结合Mapper接口和xml文件
- 首先在接口类中定义方法,具体方法在xml文件中实现。
- mapper的xml中namespace必须与接口类路径名保持一致。
- 不用再mybatis-config.xml中添加xml的映射关系。xml使用的是接口类的映射。
方法2:
复杂sql映射
除了使用 xml mapper 以外,还可以利用 @DeleteProvider @InsertProvider @SelectProvider @UpdateProvider 生成复杂sql。
public interface StudentMapper {
// 由 @DeleteProvider 注解找到一条sql语句,供 deleteByIds 方法使用
@DeleteProvider(
type = StudentSqlProvider.class,
method = "getDeleteByIdsSql"
)
void deleteByIds(List<Integer> ids);
}
public class StudentSqlProvider {
public static String getDeleteByIdsSql(@Param("list") List<Integer> aaa) {
StringBuilder sb = new StringBuilder(128);
sb.append("delete from student where sid in");
sb.append("(");
for (int i = 0; i < aaa.size(); i++) {
sb.append(aaa.get(i));
if(i < aaa.size()-1) {
sb.append(",");
}
}
sb.append(")");
return sb.toString();
}
}
4. 高级映射
表列 与 属性名 不一致 ==> 给列起别名
public interface ModuleMapper {
// 查询所有一级模块,并包含每个二级模块
public List<Module> findAll();
}
<!-- resultMap 指定返回结果中的对应关系 -->
<select id="findAll" resultMap="b">
select a.*, b.id bid, b.name bname, b.pid bpid, b.code bcode
from rbac.rbac_module a inner join rbac.rbac_module b on a.id = b.pid
</select>
<!-- id 主键映射
result 非主键映射(若列名与属性名相同 用autoMapping="true" 可不用写)
collection 为集合映射
-->
<resultMap id="b" type="domain.Module" autoMapping="true">
<!-- 映射一级模块的属性 -->
<id column="id" property="id"/>
<!-- ofType="集合中的元素类型" -->
<collection property="children" ofType="domain.Module">
<!--映射 二级模块的属性 -->
<id column="bid" property="id"/>
<result column="bname" property="name"/>
<result column="bpid" property="pid"/>
<result column="bcode" property="code"/>
</collection>
</resultMap>
上边sql语句查出的表如图:
也可以使用多个查询语句
<select id="findAll" resultMap="b">
select id,name,pid,code from rbac.rbac_module where pid=0
</select>
<resultMap id="b" type="domain.Module">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="pid" property="pid"/>
<result column="code" property="code"/>
<!-- 对 children 属性做映射
集合属性使用 collection标签映射
property="属性名字"
select="要执行的sql语句名"
column="作为条件的列"
-->
<collection property="children" select="findModule2" column="id"/>
</resultMap>
<select id="findModule2" parameterType="int" resultType="domain.Module">
select id,name,pid,code from rbac.rbac_module where pid = #{pid}
</select>
5缓存
select * from student where id = 1001 student
select * from student where id = 1001
- sqlsession 自带缓存功能,缓存没有才查库,缓存中有,直接返回缓存的结果,称为一级缓存
- 在sqlsession创建时建立,sqlsession.close 时清空
- 每个sqlsession有自己独立的缓存
二级缓存
需要额外设置,但可以允许多个 sqlsession 共享缓存数据
好处: 可以较大提升查询效率, 但是增删改频繁的情况下,不适合开启二级缓存
xml mapper 二级缓存的配置: 在 xml 映射文件中添加<cache/>
标签即可
接口 mapper 二级缓存的配置
6. 学习资料
- http://www.mybatis.org/mybatis-3/zh/index.html 官方文档,偏向基础
- 阅读源码
- 在应用上更近一步 mybatis-plus 是mybatis的扩展
https://mp.baomidou.com/