gate

发布时间 2023-08-07 13:06:28作者: MGLblog

Mybatis

1. 简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

MyBatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

1.1 持久化

数据持久化

持久化就是将程序的数据持久状态和瞬时状态转化过程

内存:断电即失

数据库(jdbc)io文件持久化

1.2 持久层

DAO层、Service层、Controller层

完成持久化工作的代码

层界十分明显

2、第一个Mybatis程序【重点】

思路:搭建环境-->导入Mybatis-->编写代码-->测试!

2.1、搭建环境

搭建数据库

CREATE DATABASE `mybatis`;

USE `mybatis`;

CREATE TABLE `user`(
  `id` INT(20) NOT NULL PRIMARY KEY,
  `name` VARCHAR(30) DEFAULT NULL,
  `pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `user`(`id`,`name`,`pwd`) VALUES 
(1,'狂神','123456'),
(2,'张三','123456'),
(3,'李四','123890')

新建项目

  1. 新建一个普通的maven项目

  2. 删除src目录

  3. 导入maven依赖

        <!--导入依赖-->
        <dependencies>
            <!--mysql驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <!--mybatis-->
            <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.2</version>
            </dependency>
            <!--junit-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
        </dependencies>
    

2.2、创建一个模块

  • 编写mybatis的核心配置文件

    <?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>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
    
    </configuration>
    
  • 编写mybatis工具类

    //sqlSessionFactory --> sqlSession
    public class MybatisUtils {
    
        private static SqlSessionFactory sqlSessionFactory;
    
        static{
            try {
                //使用Mybatis第一步:获取sqlSessionFactory对象
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        //既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例了。
        // SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。
        public static SqlSession  getSqlSession(){
            return sqlSessionFactory.openSession();
        }
    
    }
    

2.3、编写代码

  • 实体类

    package com.kuang.pojo;
    
    //实体类
    public class User {
        private int id;
        private String name;
        private String pwd;
    
        public User() {
        }
    
        public User(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.pwd = pwd;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    '}';
        }
    }
    
  • Dao接口

    public interface UserDao {
        List<User> getUserList();
    }
    
  • 接口实现类由原来的UserDaoImpl转变为一个 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=绑定一个对应的Dao/Mapper接口-->
    <mapper namespace="com.kuang.dao.UserDao">
    
    <!--select查询语句-->
       <select id="getUserList" resultType="com.kuang.pojo.User">
           select * from mybatis.user
       </select>
    
    </mapper>
    

2.4、测试

注意点:

org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry.

MapperRegistry是什么?

核心配置文件中注册 mappers

  • junit测试

    @Test
    public void test(){
        //第一步:获得SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
    
        //方式一:getMapper
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> userList = userDao.getUserList();
    
        for (User user : userList) {
            System.out.println(user);
        }
    
    
    
        //关闭SqlSession
        sqlSession.close();
    }
    

你们可以能会遇到的问题:

  1. 配置文件没有注册
  2. 绑定接口错误。
  3. 方法名不对
  4. 返回类型不对
  5. Maven导出资源问题

3. CRUD

1. namespace

<!--nameSpace要与接口的类名一致-->
    namespace="cn.gdcp.mapper.StudentMapper"

2.select

<select id="getStudentList" resultType="cn.gdcp.pojo.Student02">
    select * from dm_student
</select>

3. insert

<select id="addStudent" parameterType="cn.gdcp.pojo.Student02" resultType="java.lang.Integer">
    insert into dm_student (id,name,major,sno) values(#{id},#{name},#{major},#{sno})
</select>

4. update

<update id="updateStudent" parameterType="cn.gdcp.pojo.Student02">
    update dm_student set name=#{name},major=#{major},sno=#{sno}  where id=#{id} ;
</update>

5. delete

<delete id="deleteStudent" parameterType="int" >
    delete  from dm_student where id=#{id}
</delete>

注意点:

增删改需要提交事务

6.分析错误

  1. 标签匹配错误
  2. mapper标签的resource是路径以斜杠划分

7. 万能map

当我们实体类,或者数据库的表。字段或者参数过多,考虑用Map!

//万能map的insert
int addStudent02(Map<String,Object> map);
<insert id="addStudent02" parameterType="map" >
    insert into dm_student (id,name,major,sno) values(#{s_id},#{s_name},#{s_major},#{s_sno})
</insert>
//万能map
@Test
public void updateStudent02(){
    SqlSession session = MyBatisUtils.getSession();
    StudentMapper mapper = session.getMapper(StudentMapper.class);
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("s_id",4);
    map.put("s_name","mgl");
    map.put("s_major","boss");
    map.put("s_sno","110");
    int updateStudent02 = mapper.updateStudent02(map);
    if (updateStudent02 > 0) {
        System.out.println("修改成功!");
    }
    session.commit();
    session.close();
}

Map传递参数,直接在sql中取出key即可 [parameterType="map"]

对象传递参数,直接在sql中取出对象的属性即可[parameterType="Object"]

多个参数用Map,或者注解

8. 模糊查询

1.java代码,传递通配符% %

List<Student02> student02Like = mapper.getStudentLike02("%李%");

2.sql拼接中使用通配符% %

select * from dm_student where  name like "%"#{name}"%"

3. 配置解析

1. 核心配置文件

mybatis-config.xml

configuration(配置)

2. 属性(properties)

核心配置文件有同一字段,优先使用外部配置文件的

3. 类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。

<!--  第一种配置      -->
<!--<typeAlias type="cn.gdcp.pojo.User" alias="user"></typeAlias>-->
<!--  第二种配置  默认实体的类名就是别名,大小写不区分  首字母小写  -->
<package name="cn.gdcp.pojo"/>

如果非要改,与需要在实体上增加注释

Alias("student02")
public class Student02{}

4. 映射器mappers

  1. <!-- mapper.xml文件路径配置 -->
    <mappers>
        <mapper resource="cn/gdcp/mapper/StudentMapper.xml"></mapper>
    </mappers>
    
  2. class文件绑定注册

注意点:

接口与它的Mapper配置文件必须同名

接口与它的Mapper配置文件必须在同一包下

  1. 使用扫描包注册绑定

注意点:

接口与它的Mapper配置文件必须同名

接口与它的Mapper配置文件必须在同一包下

5. 生命周期和作用域

image-20220319212315552

生命周期、和作用域、至关重要的,因为错误的使用导致非常严重的并发问题

SqlSessionFactoryBuilder:

一旦创建了 SqlSessionFactory,就不再需要它了

局部变量

SqlSessionFactory:

想象成:数据库连接池

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。

因此 SqlSessionFactory 的最佳作用域是应用作用域

最简单的就是使用单例模式或者静态单例模式。

SqlSession:

连接到连接池的一个请求

SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

用完之后赶紧关闭,否则会占用资源

image-20220319212940313

每一个Mapper,代表一个业务

5. 解决属性名和字段名不一致的问题

1.起别名问题

select id,name as NAME,major,sno from dm_student where id=#{id}

2.resultMapper

<select id="getStudentId" resultMap="StudentMap">
        select * from dm_student where id=#{id}
    </select>
<!--    结果集映射-->
    <resultMap id="StudentMap" type="Student02">
<!--        column数据库的字段,property实体类的变量属性-->
        <id column="id" property="id"></id>
        <result column="name" property="NAME"></result>
        <result column="major" property="major"></result>
        <result column="sno" property="sno"></result>
    </resultMap>

resultMap 元素是 MyBatis 中最重要最强大的元素

ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

6. 日志

1. 日志工厂

LOG4J/STDOUT_LOGGING

STDOUT_LOGGING标准日志输出

在核心配置文件,配置我们的日志

2.LOG4J

使用

  1. 在使用log4j的类中,导入包

  2. 日志对象,参数为当前类的class

    static Logger logger = Logger.getLogger(StudentTest.class);
    
  3. 日志级别

    public void log4jTest(){
        logger.info("info:进入了log4jTest");
        logger.debug("info:进入了log4jTest");
        logger.error("info:进入了log4jTest");
    }
    

7. limit分页

limit分页

select * from Student limit satrtIndex,pageSize

mybatis

1.接口

//分页查询
List<Student02> getStudent02Limit(Map<String,Object> map);

2.Mapper.xml

<!--    分页查询-->
    <select id="getStudent02Limit" parameterType="map" resultType="Student02">
        select * from dm_student limit #{startIndex},#{pageSize}
    </select>

8. 使用注解开发

1.注解在接口上实现

@select(select * form dm_student)
List<Student02> getStudents()

2.在核心配置文件绑定接口

 <!--   绑定接口     -->
<!--        <mapper class="cn.gdcp.mapper.StudentMapper"/>-->

3.CRUD

我们可以再工具类创建的时候实现自动提交事务

public static SqlSession getSession() {
    return sqlSessionFactory.openSession(true);
}

编写接口,添加注解

//id查询
//方法存在多个参数,所有参数前同必须加上@Param("id")注解
@Select("select * from dm_student where id=#{id}")
Student02 getStudentId(@Param("id") int id);
//分页查询
@Select("select * from dm_student limit #{startIndex},#{pageSize}")
List<Student02> getStudent02Limit(Map<String,Object> map);
//insert
@Insert("insert into dm_student (id,name,major,sno) values(id=#{id},name=#{name},major=#{major},sno=#{sno})")
int addStudent(Student02 student02);
//update
@Update("update dm_student set name=#{name},major=#{major},sno=#{sno}  where id=#{id}")
int updateStudent02(Student02 student02);
//delete
@Delete("delete  from dm_student where id=#{id}")
int deleteStudent02(int id);

关于@Param()注解

基本类型的参数或者String类型,需加上

引用类型不需要加

如果只有一个基本类型,可以省去,但建议大家加上

SQL中引用的就是这里的@Param中设定的属性名

9.Lombok

常用的注解

@Data:无参构造、get、set、toString、hashCode、equals
@AllArgsConstructor

10.多对一处理【重点】

  • 多个学生,对应一个老师
  • 对于学生而言,关联多个学生,关联一个老师【多对一】

按照查询嵌套处理

<!--按照查询嵌套处理-->
<!--思路:1.查询所有的学生信息2.根据将查询出来的学生记录信息,需找对应的老师-->
    <select id="getStudent" resultMap="StudentTeacher">
        select * from student
    </select>
    <resultMap id="StudentTeacher" type="Student">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
<!--复杂的属性,需要单独处理,对象关联多对一:association,一对多集合:collection-->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"></association>
    </resultMap>
    <select id="getTeacher" resultType="Teacher">
        select * from teacher where id=#{id}
    </select>

按照结果嵌套查询

<!--    按照结果嵌套查询-->
    <!--    按照结果嵌套查询-->
    <select id="getStudent02" resultMap="StudentTeacher02">
        select s.id sid,s.name sname,t.name tname from student s,teacher t where s.tid=t.id
    </select>
    <resultMap id="StudentTeacher02" type="Student">
        <id column="sid" property="id"></id>
        <result column="sname"property="name"></result>
        <association property="teacher" javaType="Teacher">
            <result column="tname" property="name"></result>
        </association>
    </resultMap>

注意点:多对一

  • 子查询
  • 联表查询

11.一对多【重点】

对于老师而言,集合,一个老师,有很多学生【一对多】

按照查询嵌套处理

<!--    按照查询嵌套查询-->
    <select id="getStudent03" resultMap="TeacherStudent02">
        select * from teacher where id=#{tid}
    </select>
    <resultMap id="TeacherStudent02" type="Teacher">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <collection property="student" javaType="ArrayList" ofType="Student" select="getStudentTeacherId" column="id"></collection>
    </resultMap>
    <select id="getStudentTeacherId" resultType="Student">
        select * from student where tid=#{tid}
    </select>

按照结果嵌套查询

<!--    按照结果嵌套查询-->
    <select id="getTeacher02" resultMap="TeacherStudent">
        select s.id sid,s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid=t.id and t.id=#{tid}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <id column="tid" property="id"></id>
        <result column="tname" property="name"></result>
        <!--复杂的属性,需要单独处理,对象关联多对一:association,一对多集合:collection-->
<!--javaType指定属性的类型,集合中的泛型信息List,使用ofType获取-->
        <collection property="student" ofType="Student" >
            <id column="sid" property="id"></id>
            <result column="sname" property="name"></result>
            <result column="tid" property="tid"></result>
        </collection>
    </resultMap>

小结

1.关联:association[多对一]

2.集合:collection[一对多]

3.javaType & ofType

  1. javaType用来指定实体类中的属性的类型

  2. ofType:用来指定映射到list或者集合中的pojo类型,泛型中的约束类型!

3.面试高频

  • Mysql引擎
  • innoDB底层原理
  • 索引
  • 索引优化

12. 动态SQL

含义:动态sql就是指根据不同的条件生成不同的SQL语句

if
choose (when,otherwise)
trim(where,set)
foreach

IF

<select id="queryBlogIF" resultType="Blog" parameterType="map">
    select * from blog where 1=1
    <if test="title!=null">
     title=#{title}
    </if>
    <if test="author!=null">
        and author=#{author}
    </if>
</select>

choose(when,otherwise)

<select id="queryBlogChoose" resultType="Blog" parameterType="map">
    select * from blog
    <where>
        <choose>
            <when test="title!=null">
                title=#{title}
            </when>
            <when test="author!=null">
                and author=#{author}
            </when>
            <otherwise>
                and views=#{views}
            </otherwise>
        </choose>
    </where>
</select>

trim(where,set)

<select id="queryBlogIF" resultType="Blog" parameterType="map">
    select * from blog
    <where>
        <if test="title!=null">
            title=#{title}
        </if>
        <if test="author!=null">
            and author=#{author}
        </if>
    </where>
</select>
<update id="updateBlog" parameterType="map">
    update blog
    <set>
        <if test="title!=null">
            title=#{title},
        </if>
        <if test="author!=null">
            and author=#{author}
        </if>
    </set>
    where id=#{id}
</update>

SQL片段

1.使用SQL标签抽取公共部分

<sql id="if-title-author">
        <if test="title!=null">
            title=#{title}
        </if>
        <if test="author!=null">
            and author=#{author}
        </if>
    </sql>

2.需要使用的地方使用include标签引用即可

<select id="queryBlogIF" resultType="Blog" parameterType="map">
        select * from blog
        <where>
            <include refid="if-title-author"></include>
        </where>
    </select>

注意:最好基于单表使用SQL片段

Foreach

<select id="queryBlogForeach" parameterType="map" resultType="Blog">
        select * from blog
        <where>
            <foreach collection="ids" item="id" open="and (" close=")" separator="or">
                id=#{id}
            </foreach>
        </where>

    </select>

动态SQL就是在拼接SQL语句

13. 缓存

1. 简介

image-20220326092804134

2. Mybatis缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

3.一级缓存

默认情况下,只启用了本地的会话缓存,它仅仅对一次会话中的数据进行缓存

缓存失效的情况:

  1. 不同的查询

  2. 增删改操作,会改变原来的数据,必定会刷新缓存

Opening JDBC Connection
Created connection 1850180796.
==>  Preparing: select * from users where uid=?
==> Parameters: 1(Integer)
<==    Columns: uid, user_name, uage
<==        Row: 1, 张三, 20
<==      Total: 1
User(id=0, userName=张三, age=0)
==>  Preparing: update users set uage=?,user_name=? where uid=?
==> Parameters: 12(Integer), mgl(String), 2(Integer)
<==    Updates: 1
1
==>  Preparing: select * from users where uid=?
==> Parameters: 1(Integer)
<==    Columns: uid, user_name, uage
<==        Row: 1, 张三, 20
<==      Total: 1
User(id=0, userName=张三, age=0)
false
  1. 查询不同的mapper.xml

  2. 手动清理缓存

SqlSession session = MyBatisUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User userById = mapper.getUserById(1);
        System.out.println(userById);
//        int mgl = mapper.updateUser(new User(2, "mgl", 12));
//        System.out.println(mgl);
        session.clearCache();//手动清理缓存
        User userById2 = mapper.getUserById(1);
        System.out.println(userById2);
        System.out.println(userById==userById2);
        session.close();

4. 二级缓存

  1. 开启全局缓存
<!-- 开启全局缓存,默认true       -->
<setting name="cacheEnabled" value="true"/>

2.只需要在你的Mapper 映射文件中添加一行:

<!--    使用Mapper.xml开启二级缓存-->
<cache
        eviction="FIFO"
        flushInterval="60000"
        size="512"
        readOnly="true"/>

小结:

  1. 只要开启了二级缓存,在同一个mapper下就有效
  2. 所有的数据都会先放在一级缓存下
  3. 只有会话提交,或者关闭时,才会提交到二级缓存中

5. 缓存的原理

缓存的顺序:先查二级缓存,再查一级缓存,最后查数据库

image-20220326110420207

6. 自定义缓存EhCache

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干。