学习并熟练使用MyBatis
目录
- MyBatis概述
- mybatis核心配置文件
- sql映射文件
- 结果集封装
- Mapper代理开发模式
- 开发流程
- session事务管理
- 参数传递
- 参数占位符
- 参数传递
- 单个参数
- 多个参数
- 动态sql
- 单条件动态查询
- 多条件动态查询
- 多条件动态更新
- 主键返回
- 批量删除
MyBatis概述
- MyBatis是持久层的框架,java web项目分为三大架构,表现层(给用户展示的代码)、业务层(进行业务逻辑处理的代码)、持久层(与数据库进行交互的代码);
- 什么是框架,框架就是半成品软件,别人开发好的jar包,里面包含很多处理特定任务的类,所有人使用统一的框架完成特定任务,一是简化开发,不需要重复写部分通用代码,二是易于维护;
- 在使用mabatis框架之前,我们一直使用jdbc开发持久层程序,导入数据库厂商提供的jar包,也叫数据库驱动,加载包中的驱动类,连接数据库,操作数据库,最后关闭连接
- jdbc存在硬编码和重复性封装结果集的问题,开发过程比较繁琐,且维护麻烦,一旦需要改变数据库环境或者sql语句需要重新编辑源代码,编辑、部署;
5. mybatis的出现解决了jdbc硬编码和封装结果集繁琐的问题,mybatis将sql语句写到sql映射文件,又将与数据库的连接信息写入到mybatis核心配置文件,并通过一行代码返回已封装的结果集对象;
6. 两个文件均为xml格式的配置文件,mabatis底层仍然使用的是jdbc
mybatis核心配置文件
mybatis核心配置文件记录了数据库的连接信息、sql映射文件的磁盘路径、别名等信息
<?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>标签内-->
<configuration>
<!-- 配置别名,这样sql映射文件中每一条select语句返回结果集的封装对象不需要指定全路径名,且不区分大小写-->
<typeAliases>
<package name="com.zcl.pojo"/>
</typeAliases>
<!-- 配置数据库环境,可以配置多环境,通过default属性指定要使用的数据库-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 配置数据库连接池,也就是数据库连接信息,jdbc驱动类,URL(协议,dbms的Ip和端口) 用户名和密码-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.135.139:3306/mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 配置sql映射文件的磁盘路径-->
<mappers>
<mapper resource="com/zcl/dao/UserMapper.xml"/>
</mappers>
</configuration>
sql映射文件
将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">
<!-- 将所有sql语句写入mapper标签内,代码通过sql statement的id指定要执行的sql语句-->
<mapper namespace="com.zcl.dao.UserMapper">
<resultMap id="userResultMap" type="user">
<result column="username" property="userName"/>
<result column="password" property="passWord"/>
</resultMap>
<!-- 代码通过selectAll属性值找到并执行这条sql语句 -->
<select id="selectAll" resultMap="userResultMap">
select * from tb_user;
</select>
<update id="update">
update tb_user
<set>
<if test="userName != null and userName != ''">
username = #{userName},
</if>
<if test="passWord != null and passWord != ''">
password = #{passWord}
</if>
</set>
where
id = #{id};
</update>
<delete id="deleteByIds">
delete from tb_users where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
</mapper>
结果集封装
我们在编辑sql映射文件时,需要为select标签的sql statement指定封装类
<mapper namespace="com.zcl.dao.UserMapper">
<!-- 通过resultType属性指定封装类,底层原理是反射,加载user类字节码文件,实例化对象,通过字段名获取user类的特定method对象,然后给实例化的对象赋字段值-->
<select id="selectAll" resultType="user">
select * from tb_user;
</select>
<!-- 如果字段名和类属性名不一致时,就需要指定映射规则,就是不一致的字段名和属性名的映射关系-->
<select id="selectAll" resultMap="userResultMap">
select * from tb_user;
</select>
<!-- 结果集映射标签,返回结果集的字段名和封装类的属性名不一致时使用,用于匹配不同名的字段和对象属性 -->
<resultMap id="userResultMap" type="user">
<result column="username" property="userName"/>
<result column="password" property="passWord"/>
</resultMap>
</mapper>
写完sql映射文件时,我们就可以通过一行代码获取已封装成实体类对象或者集合对象的查询结果;
//通过sql映射文件的名称空间.sql statement.id的方式找到并执行对应sql语句,封装结果集数据
List<User> users = sqlSession.selectList("test.selectAll");
Mapper代理开发模式
开发流程
- 上述mybatis开发方式虽然极大简化了jdbc开发,但是也存在硬编码问题,且如果sql映射文件statement过多,容易写错,IDE也没办法帮我们指定错误并执行代码补全功能,因此mybatis提供了mapper代理开发模式
- 什么是mapper代理开发,简单说就是直接调用mapper代理对象的方法就能执行sql映射文件中的sql语句,底层用的是动态代理。
- 首先,定义Mapper接口,且sql映射文件的名称空间必须是mapper接口的全路径名,且Mapper接口的抽象方法和sql映射文件的sql statement的id属性相同,返回值类型也必须匹配。这样,我们调用mybatis生成的mapper代理对象的方法就能执行指定的sql 语句;
//定义mapper接口,方法名和sql statement的id属性相同
public interface UserMapper {
//方法返回值类型必须与sql statement 匹配,要不会报类型转换异常
List<User> selectAll();
}
<!-- sql映射文件的名称空间必须是mapper接口的全路径名-->
<mapper namespace="com.zcl.dao.UserMapper">
<!-- mapper接口方法名和id属性值相同-->
<select id="selectAll" resultType="user">
select * from tb_user;
</select>
<select id="selectAll" resultMap="userResultMap">
select * from tb_user;
</select>
<resultMap id="userResultMap" type="user">
<result column="username" property="userName"/>
<result column="password" property="passWord"/>
</resultMap>
</mapper>
这样,我们调用生成的mapper代理对象方法,就能执行指定sql语句,获取已封装的结果。
String resource = "mybatis_config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过加载mybatis核心配置文件生成SqlSessionFactory对象,内部封装了数据库连接池
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取session对象,内部封装了jdbc的connection,session 除了生成mapper代理对象,还用于管理事务
SqlSession session = sqlSessionFactory.openSession();
//获取mapper代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
//执行代理对象方法,就是执行知道sql语句,返回已封装结果集
List<User> users = mapper.selectAll();
最后代理开发模式需要注意sql映射文件的磁盘位置和文件名,
- 如果在mybatis核心配置文件标签内引用的标准格式加载sql映射文件的话,那sql映射文件放在哪起什么名字都不重要,只要在resource属性上声明正确即可,mapper代理开发的时候自动找到sql映射文件匹配mapper代理对象,只要sql映射文件的名称空间和mapper接口全路径名一样即可。
- 如果用包扫描的方式,也就是在mybatis核心配置文件标签内引用的方式扫描加载sql映射文件的话,那么在mapper代理开发时,mapper接口和对应的sql映射文件必须都在包扫描的指定路径下,且sql映射文件名和接口名相同,当然sql映射文件的名称空间也必须和接口的全路径名相同。
<mappers>
<!-- 标准格式-->
<mapper resource="com/zcl/dao/UserMapper.xml"/>
<!-- 包扫描格式-->
<package name="com.zcl.dao"/>
</mappers>
session事务管理
sqlSession 表征mybatis与数据库管理系统建立的一个连接,默认是关闭自动提交事务的,当我们通过mapper代理开发模式对数据库进行增删改操作时,最后记得提交事务;否则在操作结束断开连接后dbms会进行事务回滚;
String resource = "mybatis_config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
String username = "zcl1";
String password = "123";
User user = new User();
user.setUserName(username);
user.setPassWord(password);
userMapper.addReturnKey(user);
//操作完数据库,通过sqlSession对象提交当前事务
sqlSession.commit();
参数传递
参数占位符
- sql映射文件定义的sql语句不是固定的,可以从程序中获取参数,准确地说从代理对象方法的实参中获取参数,得到完整的执行sql,比如我们要获取id为1的记录或者id为2的记录,那么id属性值就可设定为sql statement的参数;
- mybatis提供了两种参数占位符
#{} :执行SQL时,会将 #{} 占位符替换为?发送给dbms进行检查sql语法、编译sql,生成可执行的存储函数,将来设置发送参数值给dbms,执行完整的sql语句,底层使用的PreparedStatement
${} :拼接SQL。底层使用的是 Statement ,拼接sql字符串直接发送给dbms执行,会存在SQL注入问题。
<!-- 一般我们通过#{}的形式定义sql statement参数-->
<select id="selectById" resultType="user">
select * from tb_user where id = #{id};
</select>
- 在执行mapper代理对象的方法时,mybatis将方法的实参值传入到sql参数占位符的位置
参数传递
mapper接口方法形参列表中可以定义多种类型的形参,形参个数可以是单个,也可以有多个;在生成mapper代理对象,执行对象方法时,mybatis会对传入的实参进行预处理;一般会创建一个map集合对象,然后将实参封装到map集合中,再根据参数占位符名称从map集合中取值。
单个参数
- POJO类型
直接使用,实参对象属性名和参数占位符名称一致,sql statement根据参数占位符名称直接从对象属性取值 - Map类型
直接使用,Map集合键名和参数占位符名称一致,sql statement根据参数占位符名称从Map集合取值 - Collection类型
Mybatis 会将集合封装到 map 集合中,如下:
我们可以在定义接口方法时使用 @Param 注解替换map集合中默认的 arg 键名。
map.put("arg0",collection集合);
map.put("collection",collection集合;
- List类型
Mybatis 会将集合封装到 map 集合中,如下:
同样地,我们可以使用 @Param 注解替换map集合中默认的 arg 键名。
map.put("arg0",list集合);
map.put("collection",list集合);
map.put("list",list集合);
- Array类型
Mybatis 会将数组封装到 map 集合中
同样地,我们可以使用 @Param 注解替换map集合中默认的 arg 键名。
map.put("arg0",数组);
map.put("array",数组);
多个参数
在定义mapper接口方法时,可以定义多个形参;这样调用执行mapper代理对象方法时,mybatis会创建一个Map集合对象,通过默认的键名将传入的实参值封装到Map对象中,然后mybatis再以参数占位符的名称从map集合对象中取值,得到执行sql;
map.put("arg0",参数值1);
map.put("arg1",参数值2);
map.put("param1",参数值1);
map.put("param2",参数值2);
一般我们会在定义接口方法时使用@Param注解指定map对象的键的名称
public interface UserMapper {
List<User> selectAll();
User selectById(int id);
//使用@Param注解指定map对象的键的名称
List<User> selectByCondition(@Param("userName") String username, @Param("passWord") String password);
List<User> selectByCondition(User user);
List<User> selectByCondition(Map map);
List<User> selectBySingleCondition(User user);
void add(User user);
void addReturnKey(User user);
int update(User user);
int deleteByIdInt(int id);
void deleteByIds(@Param("ids") int[] ids);
}
这样,在调用执行mapper代理对象方法时,mybatis会以@Param注解的属性值作为键名创建map对象,然后再根据参数占位符的名称从map对象中取值。
map.put("userName",username);
map.put("passWord",password);
总结:除了参数类型为Map和POJO实体类mybatis直接用之外,其他数据类型的实参都会放到Map集合对象中,我们一般会通过@Param注解指定Map集合的键名,这样,只要参数占位符的名称和@Param注解指定的键名相同即可。
动态sql
sql映射文件中的sql 语句不是固定死的,可以根据传入实参的情况动态地改变sql,比如用户可能根据两个字段值查询记录,也可能选择三个字段值查询数据,这时select语句就不能写死,where后面的条件语句必须是动态的;mybatis提供了多个标签支撑动态sql的业务需求
if
choose (when, otherwise) 相当于java中的switch(case,default)
trim (where, set)
foreach
where
set
单条件动态查询
<select id="selectBySingleCondition" resultMap="userResultMap">
select * from tb_user
<!-- where标签在这可以决定它需不需要存在,如果两个when都不成立,where就不存在,这是一个动态标签-->
<where>
<choose>
<when test="userName != null and userName != ''">
username = #{userName};
</when>
<when test="passWord != null and passWord != ''">
password = #{passWord};
</when>
<!-- <otherwise>-->
<!-- 1=1;-->
<!-- </otherwise>-->
</choose>
</where>
</select>
多条件动态查询
<select id="selectByCondition" resultMap="userResultMap">
select * from tb_user
<!-- where标签在这可以决定它需不需要存在,以及去掉后面的and关键字-->
<where>
<if test="userName != null and userName != ''">
and username = #{userName}
</if>
<if test="passWord != null and passWord != ''">
and password = #{passWord};
</if>
</where>
</select>
多条件动态更新
<update id="update">
update tb_user
<!-- set标签去掉where前面的逗号-->
<set>
<if test="userName != null and userName != ''">
username = #{userName},
</if>
<if test="passWord != null and passWord != ''">
password = #{passWord}
</if>
</set>
where
id = #{id};
</update>
主键返回
在数据添加成功后,有时候需要获取插入数据库数据的主键
<!-- keyProperty属性值指明返回的主键值放入到实参对象的id属性-->
<insert id="addReturnKey" useGeneratedKeys="true" keyProperty="id">
insert into tb_user (username,password) values (#{userName},#{passWord});
</insert>
public interface UserMapper {
void addReturnKey(User user);
}
User user = new User();
user.setUserName(username);
user.setPassWord(password);
//在执行addReturnKey方法时,mybatis将从数据库返回的主键值封装到传入的user对象的id属性中
userMapper.addReturnKey(user);
批量删除
一次删除不固定数量的记录,mybatis提供了<foreach>
标签动态遍历实参数组
//mapper接口
public interface UserMapper {
//定义数组形参,并通过@Param("ids")注解指定调用代理对象方法时创建的map集合的键的名称
void deleteByIds(@Param("ids") int[] ids);
}
<delete id="deleteByIds">
delete from tb_users where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>