一.基本概念及原理
官网地址: https://mybatis.org/mybatis-3/zh/index.html
MyBatis 是持久层框架,内部整合了JDBC,以对象的方式操作数据库.
Spring整合mybatis使用的是JDK代理
一.SpringBoot简化Mybatis操作单表
1.导入jar包文件
<!--mybatis依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--jdbc依赖包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--添加lombok的包,需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2.修改配置文件名称及编辑内容
application.properties改为application.yml
yml文件 0不解析 如果字母以0开头则引号包裹
#1.配置端口号 注意缩进!!!!!
server:
port: 8090
#2.配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
#yml文件 0不解析 如果字母以0开头则引号包裹
#password: "0123456"
password: root
#3.配置Mybatis
mybatis:
#定义别名包路径(简化UserMapping.xml文件的resultType的写法)
type-aliases-package: com.jt.pojo
#将所有的映射文件全部加载
mapper-locations: classpath:/mappers/*.xml
#开启驼峰映射
configuration:
map-underscore-to-camel-case: true
#4.打印Sql com.jt.mapper下的Sql日志
logging:
level:
com.jt.mapper: debug
3.准备POJO对象:
@Data //lombok需要pom文件添加依赖,安装lombok插件该注解会自动生成很多常用方法
@Accessors(chain = true) //重写了set方法. 可以实现链式加载
@NoArgsConstructer //无参构造函数
@AllArgsConstructor //全参数构造函数
implements Serializable对象序列化作用:
1.保证数据按照正确的格式输出
2.在多线程条件下,共享数据必须序列化生成序列号
属性类型必须是引用类型,且私有化
pojo层,修饰Data类型数据
@JsonFormat(pattern=“yyyy年MM月dd日 HH:mm:ss” , timezone=“GMT+8”)
4.将Mapper接口交给容器管理
- @Mapper //将接口交给Mybatis管理,Mybatis再将其交给Spring容器管理Map<userMapper,JDK代理对象>,作用告诉编译器这是一个负责映射的接口 (映射指的是实体类和表之间的关系)
- @MapperScan("包扫描路径")用于主启动类上,使用该注解可以省略重复写@Mapper注解
原则:Mybatis只支持单值传参将多值封装为单值
接收前段参数规则: 单个参数直接传
对象的多字段的可以使用对象封装
对象的单字段的两值可以使用集合HashMap
利用注解@Param("id")将数据封装为Map,在xml文件中使用的就是该注解中的该变量名,否则在写xml文件时,需要通过agr0,agr1...param1,param2....来标注sql语句中使用参数中的第几个参数对象的单字段的多值可以使用Arrays / list集合 / map集合封装
mapper层使用参数的规则:
1. 如果是单个参数,则使用#{key} 获取的参数的值(如果只传递一个参数,则名称任意!Mybatis单值传参时,与下标有关和名称无关)
2. 如果是对象参数,则使用#{属性} 获取的是属性值
3.如果传递的参数是Map, 则使用#{key}
4.如果传递的是Arrays / list集合 / map集合,需要遍历再取值查询返回值: 单个对象使用对象直接接收 多个对象使用List<对象User>
@Mapper //将接口交给Spring容器管理 Map<userMapper,JDK代理对象> public interface UserMapper { //根据ID查询数据库 User findUserById(int id); List<User> findUserByNA(User user); //注解:@Param("key") int minAge(值) // 作用:将数据封装为Map List<User> findUserByAge2(@Param("minAge") int minAge, @Param("maxAge") int maxAge); List<User> findListByIn(int[] array); }
关于抽象方法:
- 必须定义在某个接口中,这样的接口通常使用
Mapper
作为名称的后缀,例如AdminMapper
- Mybatis框架底层将通过接口代理模式来实现
- 方法的返回值类型:如果要执行的数据操作是增、删、改类型的,统一使用
int
作为返回值类型,表示“受影响的行数”,也可以使用void
,但是不推荐;如果要执行的是查询操作,返回值类型只需要能够装载所需的数据即可- 方法的名称:自定义,不要重载,建议风格如下:
- 插入数据使用
insert
作为方法名称中的前缀或关键字- 删除数据使用
delete
作为方法名称中的前缀或关键字- 更新数据使用
update
作为方法名称中的前缀或关键字- 查询数据时:
- 如果是统计,使用
count
作为方法名称中的前缀或关键字- 如果是单个数据,使用
get
或find
作为方法名称中的前缀或关键字- 如果是列表,使用
list
作为方法名称中的前缀或关键字- 如果操作数据时有条件,可在以上前缀或关键字右侧添加
by字段名
,例如deleteById
- 方法的参数列表:取决于需要执行的SQL语句中有哪些参数,如果有多个参数,可将这些参数封装到同一个类型中,使用封装的类型作为方法的参数类型
5.编辑UserMapper.xml映射文件
由于mybatis需要操作数据库.编辑Sql语句. 采用xml映射文件维护Sql语句.
在resource的目录中添加mappers文件创建UserMapper.xml
模糊查询 特别注意表名的大小写问题!!!
windows系统中: 不区分大小写Linux系统中: 区分大小写问题
别名包:
在配置文件中指定包路径: 可以自动的实现包路径的拼接
resultType规则:
1. 首先根据别名包匹配.设定..
2. 如果匹配不成功,则按照路径匹配.
xml转译字符: > > < < & &
万能转义:<![CDATA [ select * from user ...] ]>
注意: 新增,删除,修改没有返回值resultType
关于取值操作的说明: #{属性值},${属性值}
1.#号取值采用占位符的方式 更加安全 防止sql注入攻击!!!
2.$符一般的使用场景 以字段为参数时使用.
3.使用#号时,默认会添加一对" "号<?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"> <!-- 根节点的namespace属性用于配置此XML对应哪个接口 --> <mapper namespace="com.baidu.mybatis.mapper.AdminMapper"> <!-- 配置SQL的节点的id属性取值为抽象方法名称 --> <!-- SQL语句中的参数值使用 #{} 格式的占位符表示 --> <!-- 当配置完成后,Mybatis执行此插入数据的操作后,会将自动编号的id赋值到参数Admin admin 的id属性中,以上keyProperty指的就是将自动编号的值放回到参数对象的哪个属性中!--> <insert id="insert" useGeneratedKeys="true" keyProperty="id"> insert into ams_admin ( username, password, nickname, avatar, phone, email, description, is_enable, last_login_ip, login_count, gmt_last_login, gmt_create, gmt_modified ) values ( #{username}, #{password}, #{nickname}, #{avatar}, #{phone}, #{email}, #{description}, #{isEnable}, #{lastLoginIp}, #{loginCount}, #{gmtLastLogin}, #{gmtCreate}, #{gmtModified} ) </insert> <update id="updateUserById"> update demo_user set name=#{name} where id = #{id} </update> </mapper>
<?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"> <!--namespace是mybaits映射文件的唯一标识,与接口对应--> <mapper namespace="com.jt.mapper.UserMapper"> <!-- 语法: 1.取值操作 #{属性名称} 2.只有查询需要写resultType 3.如果是"更新"操作可以自动返回影响的行数 --> <!--resultType 返回值结果类型,返回的P0J0对象类型全路径将结果集自动的封装为对象 --> <!-- 所有select节点必须配置resultType或resultMap这2个属性中的其中1个 --> <select id="findUserById" resultType="com.jt.pojo.User"> select * from demo_user where id = #{id} </select> <!-- 如果查询结果集中的列名与类的属性名不匹配时,默认将放弃处理这些结果数据,则返回的对象中对 应的属性值为null,为了解决此问题,可以在查询时使用自定义的别名,使得名称保持一致,不过,更推荐配 置<resultMap>以指导Mybatis封装查询结果--> <select id="getById" resultMap="BaseResultMap"> select * from ams_admin where id=#{id} </select> <!-- resultMap节点的作用是:指导Mybatis如何将结果集中的数据封装到返回的对象中 --> <!-- id属性:自定义名称 --> <!-- type属性:将结果集封装到哪种类型的对象中 --> <resultMap id="BaseResultMap" type="cn.tedu.mybatis.Admin"> <!-- 使用若干个result节点配置名称不统一的对应关系 --> <!-- 在不是“一对多”的查询时,名称本来就一致的是不需要配置的 --> <!-- column属性:列名 --> <!-- property属性:属性名 --> <result column="is_enable" property="isEnable" /> <result column="last_login_ip" property="lastLoginIp" /> <result column="login_count" property="loginCount" /> <result column="gmt_last_login" property="gmtLastLogin" /> <result column="gmt_create" property="gmtCreate" /> <result column="gmt_modified" property="gmtModified" /> </resultMap> <select id="findUserByAge" resultType="User"> <![CDATA[ select * from demo_user where age > #{minAge} and age < #{maxAge} ]]> </select> <!--模糊查询注意拼接"%"#{name}"%"--> <select id="findUserByLike" resultType="User"> SELECT * FROM demo_user WHERE NAME LIKE "%"#{name}"%" </select> </mapper>
①动态Sql
动态SQL -- foreach
<foreach>
标签:用于遍历集合或数组类型的参数对象collection
属性:被遍历的参数对象,当抽象方法的参数只有1个且没有添加@Param
注解时,如果参数是List
类型则此属性值为list
,如果参数是数组类型(包括可变参数)则此属性值为array
;当抽象方法的参数有多个或添加了@Param
注解时,则此属性值为@Param
注解中配置的值item
属性:自定义的名称,表示遍历过程中每个元素的变量名,可在<foreach>
子级使用#{变量名}
表示数据separator
属性:分隔符号,会自动添加在遍历到的各元素之间<!-- 关于Mybatis的遍历的写法 foreach: 1. collection 需要遍历的集合 1.1 数组 关键字: array/list 1.2 list集合 关键字: list/array 1.3 Map<key,array/list> 关键字:key 2. open/close 循环体的开始和结束 可以写到循环之外简化标签 3. item 当前遍历数据的变量名 4. separator 分割符 --> <!-- int deleteByIds(List<Long> ids); --> <delete id="deleteByIds"> delete from ams_admin where id in ( <foreach collection="list" item="id" separator=","> #{id} </foreach> ) </delete>
动态Sql if-where条件:
动态Sql: 根据对象中不为null的属性当作where条件
语法:
1.如果判断成立,则动态拼接条件
<if test="id !=null ">条件</if>
2.where标签 去除where后边多余的一个and/or
动态Sql-set条件:
根据对象中不为null的元素,充当set条件. where id=xxx
动态Sql-choose、when、otherwise如果不想使用所有的条件可以使用choose 类似于java中的if-elseif-elseif...else 语法:
<?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.jt.mapper.UserMapper2"> <!--根据对象中不为null的属性当作where条件--> <select id="findUserList" resultType="User"> select * from demo_user <where> <if test="id !=null ">id = #{id}</if> <if test="name !=null">and name = #{name}</if> <if test="age !=null ">and age = #{age}</if> <if test="sex !=null ">and sex = #{sex}</if> </where> </select> <!--根据对象中不为null的元素,充当set条件. where id=xxx--> <update> update demo_user <set> <if test="name !=null">name = #{name},</if> <if test="age !=null">age = #{age},</if> <if test="sex !=null">sex = #{sex}</if> </set> where id = #{id} </update> <!--如果不想使用所有的条件可以使用choose 类似于java中的if-else --> <select id="findUserByNS" resultType="User"> select * from demo_user where <choose> <when test="name !=null">name = #{name}</when> <when test="age !=null">age = #{age}</when> <otherwise>sex = #{sex}</otherwise> </choose> </select> </mapper>
②Sql标签用法
说明: Sql语句中经常出现重复的数据.如果每次重复的内容都自己手写.则开发的效率低.
<!--
语法: "%" 这样的方式
-->
<select id="findUserByLike" resultType="User">
SELECT <include refid="tableColumn"/> FROM demo_user WHERE NAME LIKE "%"#{name}"%"
</select>
<!--Sql标签: 抽取公共的Sql语句 -->
<sql id="tableColumn">
id,name,age,sex
</sql>
③resultMap
说明: 在业务中经常出现该现象. 字段 user_id 属性:userId 属性和字段有驼峰映射规则.但是采用resultType的方式进行映射.则不能正常赋值.
解决方案:
1.resultMap 繁琐.
2.在配置文件中开启驼峰映射规则当表中的字段与POJO中的属性名称不一致时,需要使用resultMap的方式进行映射.
1.resultType : 单表查询&结果集字段与属性一致
2.resultMap: 可以支持 任意类型的映射 万能的结构
编辑Mapper 映射文件
<?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.jt.mapper.DogMapper">
<select id="findAll" resultMap="dogRM">
select * from dog
</select>
<resultMap id="dogRM" type="Dog">
<!--ID:代表主键-->
<id column="dog_id" property="dogId"/>
<!--结果集-->
<result column="dog_name" property="dogName"/>
<!--<result column="age" property="age"/>-->
</resultMap>
</mapper>
开启驼峰映射规则
mybatis:
#定义别名包
type-aliases-package: com.jt.pojo
#将所有的映射文件全部加载
mapper-locations: classpath:/mappers/*.xml
#开启驼峰映射
configuration:
map-underscore-to-camel-case: true
二.数据封装--Mybatis操作多表
常见关联关系:
1.一对一 一个员工对应一个部门
2.一对多 在多的表中添加
3.多对多 需要中间表进行关联
1.一对一数据封装
创建POJO对象
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class Emp implements Serializable {
private Integer id;
private String name;
private Integer age;
//关联关系: 一个员工对应一个部门
private Dept dept;
//private Integer deptId;
}
编辑Mapper 映射文件
<?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.jt.mapper.EmpMapper">
<!--
规则:
1.使用关联查询时,不能出现同名字段,否则映射报错
-->
<select id="findEmp" resultMap="empRM">
select emp.*,dept.dept_name
from emp,dept
where emp.dept_id = dept.dept_id
</select>
<!--
知识点:
1.autoMapping="true"
如果字段名称和属性名称一致,则自动映射
2.association 一对一对象
3.javaType 固定搭配 封装对象的类型
总结:Mybatis可以实现关联查询的数据封装.
可以为主对象封装数据/同时可以为引用封装数据
-->
<resultMap id="empRM" type="Emp" autoMapping="true">
<!--1.标识主键-->
<id column="id" property="id"/>
<!--2.一对一封装 Dept对象-->
<association property="dept" javaType="Dept" autoMapping="true">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
</mapper>
2. 一对多数据封装
创建POJO对象
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class Dept implements Serializable {
private Integer deptId;
private String deptName;
//关联 一个部门下有多个员工
private List<Emp> emps;
}
编辑Mapper 映射文件
<?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.jt.mapper.DeptMapper">
<select id="findAll" resultMap="deptRM">
SELECT dept.dept_name,emp.*
FROM dept,emp
WHERE emp.dept_id = dept.dept_id
</select>
<resultMap id="deptRM" type="Dept" autoMapping="true">
<!--主键必须标识-->
<id column="dept_id" property="deptId"/>
<!-- 一对多封装 固定搭配 ofType=""
columnPrefix="" 定义结果集前缀便于映射-->
<collection property="emps" ofType="Emp" autoMapping="true" columnPrefix="c_">
<id column="id" property="id"></id>
</collection>
</resultMap>
</mapper>
三.Mybatis注解开发
@Insert("SQL语句") @Update("SQL语句") @Delete("SQL语句") @Select("SQL语句")
利用注解可以根据返回值类型,自动映射
规则1: 注解和映射文件 二选一 映射文件为主导.
规则2: 注解写法一般适用于简单操作.关联查询不适用@Transactional // 业务层需要对增删改操作进行事物的控制
如果程序运行出现运行时异常,则实现事务回滚
//该Mapper主要测试注解开发
@Mapper
public interface UserAnnoMapper {
@Select("select * from demo_user")
List<User> findAll();
@Select("select * from demo_user where id=#{id}")
//@Insert()
//@Update()
//@Delete()
User findUserById(int id);
//#{变量} 会自动查找方法参数列表中的同名变量,如果没有同名变量
//会查找自定义对象类型里面的同名属性并调用get变量的方法
@Insert("insert into myemp values(null,#{name},#{sal},#{job})")
void insert(Emp emp);
}
四.Mybatis缓存
一级缓存: 在同一个sqlSession内实现数据的共享
1.Mybatis 默认条件下一级缓存默认开启的.
2.执行重复操作时,Mybatis一级缓存生效,查询一次数据库.
3.如果查询期间执行更新操作,则一级缓存清空.保证数据都是新的
springBoot整合mybatis之后,使用Mapper.find查询时.springBoot默认会开启多个sqlSession,可以通过添加事务注解@Transactional,springBoot中如果添加了事务注解,则默认采用同一个SqlSessio二级缓存:由同一个sqlSessionFactory,生产的SqlSession 数据共享. 默认开启 + 配置
1. 二级缓存默认开启.
2. 二级缓存需要标记. cache标签且关闭SqlSession3. 多线程条件下如果需要实现数据共享,则要求数据必须序列化!
要求Pojo必须序列化,配置:方案①Mapper层使用@CacheNamespace注解
方案②映射文件中加<cache/>
五.常见异常:
1.未配置application.yml
由于Mybatis依赖了JDBC的jar包. 但是该jar包文件是启动项. 当主启动类运行时,开始加载主启动项. 但是JDBC需要链接数据库.所以必须有相关的配置信息.但是此时YML文件中没有数据源的配置.所以报错.配置application.yml文件中的数据源连接信息即可
2.数据映射问题
现象及解决说明:
表中的字段名称与对象中的属性名称不一致. 结果:数据不能映射.
![在这里插入图片描述](https://img-blog.csdnimg.cn/be9bc6cfa7d44a2291a425fe9b1780cb.png)
3.结果为[ ]
如果查询的结果为[ ],数据表中有数据,则有可能是:
1.动态Sql-choose、when、otherwise中,otherwise的条件为空
2.查询条件拼接错误,导致查询条件为空,数据表中该查询字段的值又没有null值,即使有null值,查询的结果跟预期也有差异.可以查看后台打印的sql语句拼接情况,再做修改.