Mybatis
技术没有高低之分,只有用技术的人有。
1、简介
1.1、什么是Mybatis
- Mybatis是一款优秀的持久层框架
- 支持定制化SQL、储存过程及高级映射
- 几乎避免了所有的JDBC代码和手动设置参数和获取结果集
1.2、持久化
数据持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存特性:断电即失,内存中的数据就是瞬时状态
- 数据库中的数据就是持久状态
为什么需要持久化?
- 有一些对象(数据),不能让他丢失
1.3、持久层
Dao层
- 完成持久化工作的代码
- 层界限十分明显
1.4、为什么需要MyBatis
- 帮助我们将数据存入数据库
- 方便
- 传统JDBC太复杂了。简化、框架、自动化
- 不用Mybatis也行。
- 优点
- 灵活,简单易学
- SQL和代码的分离,提高维护性
- 提供xml标签映射
- 最重要的一点:使用的人多!
2、创建mybatis项目
2.1、新建项目
导入pom.xml依赖
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
数据库配置文件:db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
username=root
password=123456
导入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 resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<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>
<!--注册mapper.xml文件,绑定对于的mapper接口-->
<mappers>
<!--resource必须是/-->
<mapper resource="com/lvboaa/mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
新建mybatis工具包
public class MybatisUtils {
private static SqlSessionFactory sessionFactory;
static {
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//SqlSession包含了数据库执行的所有代码
public static SqlSession getSqlSession(){
//sessionFactory.openSession(true);设置autocommit=true
return sessionFactory.openSession();
}
}
新建实体类和mapper层接口
新建mapper对应的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">
<!--namespace绑定对应mapper接口-->
<mapper namespace="com.lvboaa.mapper.UserMapper">
<!--根据id查用户,去掉parameterType也能成功,即使是对象-->
<select id="getUserById" parameterType="int" resultType="com.lvboaa.pojo.User">
select * from user where id = #{id}
</select>
</mapper>
测试
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//获取mapper对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> list = userMapper.getAllUser();
for(User user:list){
System.out.println(user.toString());
}
//增删改需要提交事务:sqlSession.commit();
sqlSession.close();
}
解决配置文件找不到的问题
<!--解决配置文件不生效的问题,pxm.xml-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
3、CRUD操作
3.1、万能map
实体类或者数据库中字段过多,应当考虑map
<update id="updateUser1" parameterType="map">
update user set name=#{username} where id=#{userid}
</update>
3.2、模糊查询
在传参的时候加通配符
List<User> list = userMapper.getUserLike("%李%");
在sql语句中加通配符
select * from user where name like "%"#{value}"%"
4、配置解析
4.1、环境配置
Mybatis默认事务管理器是JDBC,默认连接池是POOLED,可以配置多套数据库环境,但每个SqlSessionFactory只能选择一种
4.2、给实体类取别名,就不需要用全限定类名了
xml配置
<typeAliases>
<!--包-->
<package name="com.lvboaa.pojo"/>
<!--单个类取别名-->
<!--<typeAlias type="com.lvboaa.pojo.User" alias="User"/>-->
</typeAliases>
注解
- 可在设置包的前提下修改成其他的别名:
@Alias("user")
4.3、映射器(mapper)
使用resource文件扫描绑定
<mappers>
<mapper resource="com/lvboaa/mapper/UserMapper.xml"></mapper>
</mappers>
使用class文件扫描绑定
<mappers>
<!--接口和mapper.xml文件必须同名,并且在同一个包下-->
<mapper class="com.lvboaa.mapper.UserMapper"></mapper>
</mappers>
扫描包也是
<package name="com.lvboaa.mapper"/>
4.4、生命周期和作用域
错误的使用会导致严重的并发问题
Mybatis项目执行过程
SqlSessionFactoryBuilder:
- 一旦创建SqlSessionFactory,就不再需要了
- 方法作用域,局部变量
SqlSessionFactory
- 可以想象成数据库连接池
- 一旦创建一直存在,不能丢弃和重新创建一个实例
- 作用域是应用作用域(application),全局变量
- 最简单地就是使用单例模式或者静态单例模式
SqlSession
- 线程不安全,不能被共享,最佳作用域是方法作用域,用完之后赶紧关闭,否则资源会浪费
- 是连接连接池的一个请求
Mapper
- 通过SqlSession得到Mapper对象
- 每个Mapper代表一个具体的业务
4.5、解决属性名和字段不匹配的问题
使用别名
- mybatis默认基本类型的别名为int:_int
- int代表的是它的包装类Integer
<select id="getUserById" parameterType="int" resultType="user">
select id,name,pwd password from user where id = #{id}
</select>
使用resultMap
<resultMap id="UserMap" type="user">
<!--可以只设置不匹配的字段-->
<result property="password" column="pwd"/>
</resultMap>
<select id="getUserById" parameterType="int" resultMap="UserMap">
select * from user where id = #{id}
</select>
5、日志
5.1、日志的作用
- 日志最主要就是排错处理需要用到,能看到现在在做什么
- 以前是通过sout和debug排错
5.2、常用日志工厂
STDOUT_LOGGING:标准日志工厂
<settings>
<!--标准日志工厂,mybaits.xml-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
LOG4J
- 可以控制日志输出目的地
- 可以控制日志的输出级别和输出格式
- 可以通过修改配置文件灵活配置,而不需要修改代码
LOG4J配置
- pom.xml导入依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=【%c】-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=【%p】【%d{yy-MM-dd}】【%c】%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
- 配置LOG4J
<settings>
<!--mybaits.xml-->
<setting name="logImpl" value="LOG4J"/>
</settings>
- LOG4J简单使用
static Logger logger = Logger.getLogger(UserDaoTest.class);
@Test
public void logTest(){
//在log4j.properties中设置打印级别,就只会打印高于或等于该级别的信息
//fatal>error>warn>info>debug
logger.debug("debug.....");
logger.info("info......");
logger.warn("warn.....");
logger.error("error.....");
}
6、分页
主要是减少数据的处理量,提升效率
6.1、SQL的limit实现
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from user limit #{startIndex},#{pageSize}
</select>
public void getUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//起始页和页面大小
Map map = new HashMap<String,Integer>();
map.put("startIndex",2);
map.put("pageSize",2);
List<User> list = userMapper.getUserByLimit(map);
for (User user:list){
System.out.println(user.toString());
}
sqlSession.close();
}
6.2、RowBounds实现(不推荐使用)
<select id="getUserByRowBounds" resultMap="UserMap">
select * from user
</select>
public void getUserByRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(1, 2);
List<User> list = sqlSession.selectList("com.lvboaa.mapper.UserMapper.getUserByRowBounds",null,rowBounds);
for (User user:list){
System.out.println(user.toString());
}
sqlSession.close();
}
6.3、分页插件
MyBatis PageHelper
7、Mybatis注解开发
- 简单sql用注解,复杂sql用xml(易于维护)
- 比如说属性和字段不匹配注解就不能解决这个问题
- 真实项目mybaits用xml,spring和springMVC用注解
面向接口编程
注解开发
- 本质是使用反射机制实现(也是一切Java框架的机制)
- 底层是动态代理
<!--mybatis.xml找到对应接口-->
<mappers>
<mapper class="com.lvboaa.mapper.UserMapper"/>
</mappers>
@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int id);
#{}和${}的区别
- #sql解析时会在参数加上
" "
,把参数当成字符串,相当于PreparedSatetment,能有效防止sql注入 - 而$是将参数直接拼接在sql语句中,相当于Statement,不能防止sql注入
- order by使用动态参数只能使用${}
8、Mybatis执行流程
9、多表处理
9.1、多对一
多个学生,对应一个老师
- 对于学生而言,关联:多个学生关联一个老师【多对一】
- 对于老师而言,集合:一个老师教很多学生【一对多】
按照查询嵌套处理,子查询(不建议使用)
<!--实体类字段-->
@Data
public class Student {
private int id;
private String name;
private Teacher teacher;
}
@Data
public class Teacher {
private int id;
private String name;
}
<select id="getAllStudent" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="student">
<!--属性和字段相同的可加可不加-->
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--复杂的属性单独处理对象使用association,集合使用collection-->
<association property="teacher" column="tid" javaType="teacher" select="getTeacherById"/>
</resultMap>
<select id="getTeacherById" resultType="teacher">
select * from teacher where id=#{pid}
</select>
按照结果嵌套处理,联表查询,推荐使用
<select id="getAllStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.id tid,t.name tname from student s,teacher t where s.tid=t.id
</select>
<resultMap id="StudentTeacher2" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
9.2、一对多
@Data
public class Student {
private int id;
private String name;
private int tid;
}
@Data
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
多表联合查询,推荐
<select id="getTeacherById" resultMap="TeacherStudent">
select s.id sid,s.name sname,s.tid stid,t.id tid,t.name tname
from student s,teacher t
where s.tid=t.id and t.id=#{id}
</select>
<resultMap id="TeacherStudent" type="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--ofType就是集合里面的泛型-->
<collection property="students" javaType="ArrayList" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="stid"/>
</collection>
</resultMap>
子查询
<select id="getTeacherById2" resultMap="TeacherStudent2">
select * from teacher where id=#{id}
</select>
<resultMap id="TeacherStudent2" type="teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" javaType="ArrayList" ofType="student" select="getStudentById" column="id"/>
</resultMap>
<select id="getStudentById" resultType="student">
select * from student where tid=#{id}
</select>
9.3、注意点
- 保证SQL的可读性,尽量保证通俗易懂
- 注意一对多和多对一中属性和字段的问题
- 如果问题不好排查,使用日志,推荐LOG4J
- 避免慢SQL,加索引
面试高频
- MySQL引擎:InnoDB,MyIs…
- InnoDB底层
- 索引
- 索引优化
10、动态SQL
根据根据不同的条件生成不同的SQL语句
驼峰命名和数据库字段匹配的问题
<settings>
<!--开启驼峰命名和下划线命名的转换-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
10.1、if
<select id="selectBlogIf" parameterType="map" resultType="blog">
select * from blog where 1=1
<if test="author != null">
and author=#{author}
</if>
<if test="title != null">
and title=#{title}
</if>
</select>
10.2、where
没有成立的不会加
where
,如果成立的语句开头为and
或者or
会去掉
<select id="selectBlogIf1" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="author != null">
author=#{author}
</if>
<if test="title != null">
and title=#{title}
</if>
</where>
</select>
10.3、choose
相当于Java的switch…case…
<select id="selectBlogIf2" parameterType="map" resultType="blog">
select * from blog
<where>
<choose>
<when test="author != null">
author=#{author}
</when>
<when test="title != null">
title=#{title}
</when>
<!--至少会走这-->
<otherwise>
views=#{views}
</otherwise>
</choose>
</where>
</select>
10.4、set
主要用于update,会自动排除不需要的逗号
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="author!=null">
author=#{author},
</if>
<if test="title!=null">
title=#{title}
</if>
</set>
where id=#{id}
</update>
10.5、trim
可自定义去除字符串
<trim prefix="where" prefixOverrides="and | or"/>
<trim suffix="set" suffixOverrides=","/>
``
### 10.6、SQL片段
>将SQL片段取出来,方便复用
```xml
<sql id="sql-author-title">
<if test="author != null">
author=#{author}
</if>
<if test="title != null">
and title=#{title}
</if>
</sql>
<select id="selectBlogIf1" parameterType="map" resultType="blog">
select * from blog
<where>
<include refid="sql-author-title"/>
</where>
</select>
注意:
- 最好基于单表定义SQL片段
- 不要存在
where
和set
片段 - 最好只有
if
判断
10.6、foreach
对一个集合遍历,通常用在
in
语句的时候
<select id="selectBlogIn" parameterType="map" resultType="blog">
select * from blog where id in
<foreach collection="list" open="(" separator="," close=")" item="id">
#{id}
</foreach>
</select>
动态SQL就是在拼接SQL语句,只需要保证SQL的正确性,按照格式去排列组合就行了
- 可以先写完整SQL,再去修改成动态SQL
11、缓存
问题
- 查询:连接数据库,耗资源
- 把一次查询的结果暂放在可以取到的地方–>内存
- 然后再次查询的时候就不用走数据库了,直接走缓存
11.1、什么是缓存
- 存在内存中的临时数据
- 用户在缓存中查询数据,提高查询效率,解决高并发的性能问题
为什么使用缓存
- 减少和数据库的交互次数,减少系统开销,提高系统效率
什么样的数据能使用缓存
- 经常查询并且不经常改变的数据
11.2、Mybatis缓存
- Mybatis可以非常方便地定制和配置缓存,可以极大地提高查询效率
- Mybatis默认定义:一级缓存和二级缓存
一级缓存
- 会将一个sqlsession中的查询放在本地缓存中,需要获取相同的数据,直接在缓存中拿
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
....
sqlSession.close();
- 默认开启一级缓存,不能关闭,作用域为 SqlSession,拿到
session
到clearCache
或close
之后,该Session
中的所有Cache
清空。一级缓存最多缓存 1024 条 SQL。默认使用最少使用(LRU)算法清除不需要的缓存 - 缓存失效
- 查询不同的东西
- 增删改操作(可能会改变原来的数据,必定会删除数据)
- 使用不同的mapper
- 手动清除缓存
sqlSession.clearCache();
二级缓存
- 二级缓存需手动配置开启,作用域是Mapper(Namespace),同一个mapper才有效
- 只有当一级缓存在
SQLSession
会话关闭或刷新缓存后,才会将一级缓存保存在二级缓存中
<!--mybatis.xml-->
<settings>
<!--显式开启全局缓存,默认值为true-->
<setting name="cacheEnabled" value="true"/>
</settings>
<!--mapper.xml-->
<!--开启二级缓存,不配置任何东西,使用默认的,一般用这个-->
<cache/>
<!--自定义参数-->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
<!--引用缓存-->
<cache-ref namespace=""/>
SqlSession sqlSession = MybatisUtils.getSqlSession();
//获取mapper对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1=userMapper.getUserById(1);
System.out.println(user1);
sqlSession.close();
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user2=userMapper1.getUserById(1);
System.out.println(user2);
//同一个对象
System.out.println(user1==user2);
sqlSession.close();
- 可在mapper.xml中的select标签设置
useCache=false
关闭二级缓存 - 在C/U/D标签设置
flushCache=false
不刷新缓存
注意
- 一级、二级都是基于 PerpetualCache 的 HashMap 本地缓存
- 当某一个作用域(一级缓存 Session/二级缓存NameSpace)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
自定义缓存:EhCache
- 不用,一般用Redis数据库来做缓存
缓存原理