Mybatis

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

  1. 可以控制日志输出目的地
  2. 可以控制日志的输出级别和输出格式
  3. 可以通过修改配置文件灵活配置,而不需要修改代码

LOG4J配置

  1. pom.xml导入依赖
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. 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
  1. 配置LOG4J
<settings>
    <!--mybaits.xml-->
    <setting name="logImpl" value="LOG4J"/>
</settings>
  1. 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片段
  • 不要存在whereset片段
  • 最好只有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,拿到sessionclearCacheclose 之后,该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数据库来做缓存

缓存原理

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值