怀旧网,博客详情:Mybatis 详细介绍

1、Mybatis 详细介绍

2、Spring 详细介绍

3、SpringMVC 详细介绍

4、Vue 使用详细介绍

5、Spring Boot 介绍

6、Spring Boot 配置文件讲解

7、Spring Security 介绍

8、Shiro 介绍

9、Spring Boot 整合 Swagger

10、Spring Boot 任务

11、Redis 详解介绍

12、Docker 使用详细介绍

13、JVM 介绍

14、JUC并发编程

原创

Mybatis 详细介绍

简介

image-20240402093738694

1、什么是Mybatis

  • Mybatis 是一款优秀的持久层框架
  • 它支持定制化SQL、存储过程以及高级映射
  • Mybatis 避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
  • Mybatis 可以使用建的的XML或注解来配置和映射源生类型、接口和Java的POJO(Plain Old Java Objects, 普通老式Java对象)为数据库中的记录
  • MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了[google code](https://baike.baidu.com/item/google code/2346604?fromModule=lemma_inlink),并且改名为MyBatis
  • 2013年11月迁移到Github

如何获得Mybatis

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

2、持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据库(jdbc),io文件持久化

为什么需要持久化?

  • 有一些对象不能让他丢掉
  • 内存太贵了

3、持久层

Dao层、Service层、Controller层.....

  • 完成持久化工作的代码块
  • 层界限十分明显

4、为什么需要Mybatis?

  • 帮助我们将数据存入到数据库中

  • 方便

  • 传统的JDBC代码太复杂了。简化。框架。自动化。

  • 不用Mybatis也可以。更容易上手。技术没有高低之分

  • 优点

    • 简单易学
    • 灵活
    • sql和代码的分离,提高了可维护性
    • 提供映射标签,职场对象与数据库的orm字段关系映射
    • 提供对象关系映射标签,职场对象关系组件维护
    • 提供xml标签,支持编写动态sql

最重要的一点:使用的人很多!

第一个 Mybatis 程序

思路:数据库表创建-->项目搭建导包-->配置文件编写-->编写工具类-->编写代码-->测试!

1、数据库表创建

-- 创建一个数据库
CREATE DATABASE `mybatis`;

-- 切换到创建的数据库
USE `mybatis`;

-- 创建一张表
CREATE TABLE `user`(
    `id` INT NOT NULL PRIMARY KEY,
    `name` VARCHAR(20),
    `password` VARCHAR(20)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

-- 插入几条数据
INSERT INTO `user`(`id`, `name`, `password`)
VALUES(1, '怀旧', '123456'),
(2, '小旧', '654321'),
(3, '小旧', 'root');

-- 查询当前数据
SELECT * FROM `user`;

image-20240402101747397

2、项目搭建导包

项目创建完成编写pom.xml文件导入所需依赖

  • mysql驱动
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.17</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.2</version>
</dependency>

image-20240402102939171

确保包都成功下载下来了

3、配置文件编写

在resources目录下创建一个mybatis的配置文件(文件名建议 : mybatis-config.xml)

image-20240402104151095

<?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>
    <!-- 配置链接环境(可以配置多套)  -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
        <!-- 第二组测试-在使用的时候需要写id的名称就可以完成数据库的切换 -->
        <environment id="test">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${test.driver}" />
                <property name="url" value="${test.url}" />
                <property name="username" value="${test.username}" />
                <property name="password" value="${test.password}" />
            </dataSource>
        </environment>
    </environments>
</configuration>

创建正式使用的环境--修改配置中的连接地址和用户名等信息

<environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <!-- 在xml配置文件中,& 符号需要使用 &amp; 来转义 -->
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;charsetEncoding=UTF-8&amp;serverTimezone=UTC" />
                <property name="username" value="root" />
                <property name="password" value="123456" />
            </dataSource>
        </environment>
    </environments>

4、编写工具类

代码结构:

image-20240402105028707

编写工具类代码(获取数据库连接对象)

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

// 因为需要操作的步骤都是固定的,需要要写
public class MybatisUtil {
    private static SqlSessionFactory sqlSessionFactory;

    // 使用mybatis工具必须执行的以下三步
    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库连接的对象
     * @return SqlSession 对象(等价与 JDBC 的 Connection)
     */
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

5、编写代码

编写实体类(与数据库的user表对应):

image-20240402110206974

package com.huaijiuwang.pojo;


public class User {
    private int id;
    private String name;
    private String password;


    public User() {
    }

    public User(int id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    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 getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String toString() {
        return "User{id = " + id + ", name = " + name + ", password = " + password + "}";
    }
}

编写dao层的接口:

image-20240402110426929

package com.huaijiuwang.dao;

import com.huaijiuwang.pojo.User;
import java.util.List;

public interface UserDAO {
    // 获取所有的用户数据
    List<User> getUserList();
}

创建和UserDAO映射操作数据库的xml配置文件

image-20240402110847274

<?xml version="1.0" encoding="UTF8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace绑定一个对应的dao,让配置文件和接口能够对应上 -->
<mapper namespace="com.huaijiuwang.dao.UserDAO">
    <!-- 要编写查询语句就直接写select标签 里面id对应要dao中的方法名,resultType对应到返回值的类型,这边必须写全类名  -->
    <select id="getUserList" resultType="com.huaijiuwang.pojo.User">
        select * from `user`;
    </select>
</mapper>

6、测试运行

使用junit测试

image-20240402111718667

注意:建议测试的包最好和需要测试的包一一对应

public class UserDAOTest {
    @Test
    public void testUserDAO(){
        // 1. 获取 SqlSession 对象
        SqlSession sqlSession = MybatisUtil.getSqlSession();

        // 2. 通过 sqlSession 对象获取DAO对象用来执行sql
        UserDAO userDAO = sqlSession.getMapper(UserDAO.class);

        // 3. 通过 userDAO 执行查询获取数据
        List<User> userList = userDAO.getUserList();

        // 4. 输出查询结果
        System.out.println(userList);

        // 5. 最后记得关闭 sqlSession
        sqlSession.close();
    }
}

image-20240402112326048

运行报错:

注意:每一个 xxxMaper.xml 文件都需要在我们的mybatis-config.xml 文件中注册不然识别不到

<!-- 每一个mapper.xml文件都需要在下面进行注册,不然不能使用 -->
<mappers>
    <!-- 配置 UserDAO 注册 resource 写对应mapper.xml的全路径(以 / 分割,不能使用 . 来写了) -->
    <mapper resource="com/huaijiuwang/dao/UserMapper.xml" />
</mappers>

image-20240402112830052

运行再次报错,报错说没有当前的UserMapper.xml文件

查看打包的 target 目录

image-20240402112956305

配置的xml存在,但是写在java目录下的xml文件没有导入进来

解决方案两种:

  1. 修改pom.xml 添加资源打包,打包java目录下的资源
  2. 将mapper.xml文件放在resources目录下即可

方式一解决:

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <!-- 添加后就可以扫码到.xml文件了 -->
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

image-20240402113606823

配置完成后,xml文件成功被加载了;

再次运行输出如下:

image-20240402114559087

成功查询到数据库中的数据了;

方式二解决:

image-20240402132230665

修改xml文件的位置

注意:

  • 将xml文件放在resources目录下最好创建与UserDAO同样的目录来存放
  • 在mybatis-onfig.xml文件中配置的路径需要和当前存放xml文件的位置保持对应

image-20240402132453081

image-20240402132500639

运行测试:

image-20240402132521116

成功输出结果

image-20240402132615765

输出目录加载的位置也没有问题;

image-20240402132729929

第二种执行sql的方法(不建议使用)

public void testUserDAO(){
    // 1. 获取 SqlSession 对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    // 2. 直接获取数据(不推荐默认返回 Object 对象需要强转)
    List<Object> userList = sqlSession.selectList("com.huaijiuwang.dao.UserDAO.getUserList");

    // 输出打印查询结果
    System.out.println(userList);

    // 3. 关闭 sqlSession
    sqlSession.close();
}

image-20240402133443100

通过 Mybatis 实现 CRDU

1、namespace

namespace 中的包名要喝 DAO 接口的包名一致!

2、select 语句

image-20240402141122827

在编写一个,通过id查询用户数据

// 根据 id 获取用户数据
User getUserById(int id);

编写对应xml文件

<select id="getUserById" parameterType="int" resultType="com.huaijiuwang.pojo.User">
    select * from `user` where `id` = #{id}
</select>

注意:

  • id: 需要对应到接口中的方法名

  • parameterType: 传递进来的参数的名称

  • resultType : 返回数据的类型

  • #{ xxx }: 用来取传入参数的值对应到方法中的参数名称,需要一样

    例如修改接口为 User getUserById(int id1); 那么查询语句就需要修改为 select * from user where id = #{id1}

编写测试代码:

@Test
public void testUserDAO(){
    // 1. 获取 SqlSession 对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    // 2. 通过 sqlSession 对象获取DAO对象用来执行sql
    UserDAO userDAO = sqlSession.getMapper(UserDAO.class);

    // 3. 通过 userDAO 执行查询获取数据
    User user = userDAO.getUserById(1);

    // 4. 输出查询结果
    System.out.println(user);

    // 5. 最后记得关闭 sqlSession
    sqlSession.close();
}

image-20240402140732452

修改查询id为2的

// 3. 通过 userDAO 执行查询获取数据
User user = userDAO.getUserById(2);

image-20240402140756811

3、insert 语句

  1. 编写接口
// 插入一条用户数据
int addUser(User user);
  1. 编写mapper.xml
<insert id="addUser" parameterType="com.huaijiuwang.pojo.User">
    insert into `user` values(#{id}, #{name}, #{password})
</insert>
  • 直接使用insert标签代表这是一条插入sql
  • 参数类型就写我们传入的User对象的全类名
  • 里面的插入数据,就通过 #{xxx} 来进行获取数据,注意:这边获取数据的时候是直接填写在java实体类的属性名就可以,不用写变量名点出来

image-20240402142431052

​ 和这个对应

  1. 编写测试
@Test
public void testUserDAO(){
    // 获取 SqlSession 对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    // 通过 sqlSession 对象获取DAO对象用来执行sql
    UserDAO userDAO = sqlSession.getMapper(UserDAO.class);

    // 插入一条数据
    int i = userDAO.addUser(new User(4, "小明", "123321"));
    // 判断数据是否成功插入
    if(i > 0){
        System.out.println("插入数据成功!");
    }else{
        System.out.println("插入数据失败!");
    }

    // 通过 userDAO 执行查询获取数据
    List<User> userList = userDAO.getUserList();

    // 输出查询结果
    userList.forEach(System.out::println);

    // 关闭 sqlSession
    sqlSession.close();
}

image-20240402142042257

注意这边会有问题:我们查看数据库

image-20240402142103159

发现数据并没有插入到数据库中

原因是sql执行没有提交:手动配置提交插入(一定要在关闭连接和执行插入中间提交)

// 提交修改
sqlSession.commit();

// 关闭 sqlSession
sqlSession.close();

image-20240402142237472

image-20240402142243627

再次运行,数据库数据成功添加进去了

4、update 语句

和插入一样直接上代码:

// 修改用户数据
int updateUser(User user);
<update id="updateUser" parameterType="com.huaijiuwang.pojo.User">
    update `user` set `name` = #{name}, `password` = #{password} where `id` = #{id}
</update>
@Test
public void testUserDAO(){
    // 获取 SqlSession 对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    // 通过 sqlSession 对象获取DAO对象用来执行sql
    UserDAO userDAO = sqlSession.getMapper(UserDAO.class);

    // 插入一条数据
    int i = userDAO.updateUser(new User(2, "小怀", "123321"));
    // 判断数据是否成功插入
    if(i > 0){
        System.out.println("修改数据成功!");
    }else{
        System.out.println("修改数据失败!");
    }

    // 通过 userDAO 执行查询获取数据
    List<User> userList = userDAO.getUserList();

    // 输出查询结果
    userList.forEach(System.out::println);

    // 提交修改
    sqlSession.commit();

    // 关闭 sqlSession
    sqlSession.close();
}

image-20240402142951213

image-20240402142956708

成功修改;

5、delete 语句

// 根据用户 id 删除一个用户
int deleteUserById(int id);
<delete id="deleteUserById" parameterType="int">
    delete from `user` where `id` = #{id}
</delete>
@Test
public void testUserDAO(){
    // 获取 SqlSession 对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    // 通过 sqlSession 对象获取DAO对象用来执行sql
    UserDAO userDAO = sqlSession.getMapper(UserDAO.class);

    // 插入一条数据
    int i = userDAO.deleteUserById(4);
    // 判断数据是否成功插入
    if(i > 0){
        System.out.println("删除数据成功!");
    }else{
        System.out.println("删除数据失败!");
    }

    // 通过 userDAO 执行查询获取数据
    List<User> userList = userDAO.getUserList();

    // 输出查询结果
    userList.forEach(System.out::println);

    // 提交修改
    sqlSession.commit();

    // 关闭 sqlSession
    sqlSession.close();
}

image-20240402143309525

image-20240402143314505

删除数据成功;

6、分析错误

image-20240402144056471

7、配置自动提交事务

/**
     * 获取数据库连接的对象
     * @return SqlSession 对象(等价与 JDBC 的 Connection)
*/
public static SqlSession getSqlSession(){
    // 参数设置为true,代表事务默认自动提交(默认为false)
    return sqlSessionFactory.openSession(true);
}

执行sql的时候需要多个参数

例如现在的需求是需要修改一个用户,根据 id 修改一个用户,但是id同时可以被修改,就意味着需要传一个用户对象,还需要一个id,需要多个参数;

解决方案,使用Map对象来实现,

编写接口:

// 根据 id 修改用户数据,id也可以被修改
int updateUserById(Map<String, Object> map);

传入的参数为map类型就可以了

编写mapper.xml

<update id="updateUserById" parameterType="map">
    update `user` set `id` = #{newId}, `name` = #{name}, `password` = #{password} where `id` = #{oldId}
</update>

参数为map对象的时候,需要编写 parameterType 为小写 map 类型,获取数据的时候,需要写的是map中的键就可以了;

@Test
public void testUserDAO(){
    // 获取 SqlSession 对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    // 通过 sqlSession 对象获取DAO对象用来执行sql
    UserDAO userDAO = sqlSession.getMapper(UserDAO.class);

    // 创建一个map对象用来存修改参数
    HashMap<String, Object> map = new HashMap<>();
    map.put("oldId", 2);
    map.put("newId", 10);
    map.put("name", "怀旧111");
    map.put("password", "asdfg");

    // 插入一条数据
    int i = userDAO.updateUserById(map);
    // 判断数据是否成功插入
    if(i > 0){
        System.out.println("修改数据成功!");
    }else{
        System.out.println("修改数据失败!");
    }

    // 通过 userDAO 执行查询获取数据
    List<User> userList = userDAO.getUserList();

    // 输出查询结果
    userList.forEach(System.out::println);

    // 提交修改
    sqlSession.commit();

    // 关闭 sqlSession
    sqlSession.close();
}

image-20240402145530637

成功修改数据;

如果不使用map也可以解决,例如目前这个情况,我们其实可以在加一个实体类,

public class User1 {
    private int newId;
    private int oldId;
    private String name;
    private String password;
}

在传入的参数类型就可以写User1这个对象了;

或者使用注解方式来实现....

配置解析

1、核心配置文件

image-20240402184529636

2、环境配置

image-20240402192554716

3、属性(properties三)

image-20240402193821236

image-20240402193811399

编写一个配置文件

db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&charsetEncoding=UTF-8&serverTimezone=UTC
username=root
password=123456

在 mybatis-config.xml 引入外部配置文件:

<!-- 引入外部配置文件 -->
<properties resource="db.properties" />

引入后就可以将我们dataSource里面的属性值换为引入的配置文件的值了:

<!-- 配置链接环境(可以配置多套)  -->
<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC" />
        <dataSource type="POOLED">
            <property name="driver" value="${driver}" />
            <!-- 在xml配置文件中,& 符号需要使用 &amp; 来转义 -->
            <property name="url" value="${url}" />
            <property name="username" value="${username}" />
            <property name="password" value="${password}" />
        </dataSource>
    </environment>
</environments>

测试之前运行的代码:

image-20240402194607736

还有一种方式,可以不引入外部的

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&charsetEncoding=UTF-8&serverTimezone=UTC

删除配置文件中的用户名密码,在配置文件中写

<!-- 引入外部配置文件 -->
<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</properties>

测试运行:

image-20240402194759664

同样运行成功!

注意:当两个地方存在同样的一个键的时候,优先使用的是外部引入的键的值.

4、类型别名(typeAliases)

image-20240402195054205

使用测试:

为User类创建别名

<!-- 给类取别名 -->
<typeAliases>
    <typeAlias alias="User" type="com.huaijiuwang.pojo.User" />
</typeAliases>

原始mapper.xml文件

<select id="getUserList" resultType="com.huaijiuwang.pojo.User">
    select * from `user`;
</select>

修改后:

<select id="getUserList" resultType="User">
    select * from `user`;
</select>

运行测试:

image-20240402195432806

执行依然没问题;

去掉别名测试:

image-20240402195601040

运行报错,说明能查出来是别名起效果了;

编写方式二:

通过包配置,包下面的所有类添加别名(最常用),它默认取的别名就是类的名称(小写大写都可以):

<!-- 给类取别名 -->
<typeAliases>
    <package name="com.huaijiuwang.pojo"/>
</typeAliases>
<select id="getUserList" resultType="user">
    select * from `user`;
</select>

运行测试:

image-20240402195840767

依然没问题;

建议:在类比较多的时候使用方式二,类比较少可以使用方式一;

区别:第一种可以自定义名称,但是第二种不行,必须系统给什么名称就是什么名称(但也不完全是,后面可以通过注解来实现);

5、设置

image-20240402201400425

6、其他配置

image-20240402201738392

7、映射器(mappers)

方式一:原始方式(配置xml文件路径)【推荐使用】

<mappers>
    <!-- 配置 UserDAO 注册 resource 写对应mapper.xml的全路径(以 / 分割,不能使用 . 来写了) -->
    <mapper resource="com/huaijiuwang/dao/UserMapper.xml" />
</mappers>

方式二:配置类绝对路径方式

<mappers>
    <mapper class="com.huaijiuwang.dao.UserDAO" />
</mappers>

image-20240402202305723

运行报错

image-20240402202328588

原因:名称没有修改为一样的

image-20240402202531483

必须修两个文件名相同:

image-20240402202606180

注意点:

  • 接口和它的Mapper配置文件必须同名!
  • 接口和它的Mapper配置文件必须在同一个包下!

方式三:扫描包的方式【其次使用(当包很多的情况下)】

<mappers>
    <package name="com.huaijiuwang.dao"/>
</mappers>

注意点和方式二一样,两个点都需要注意

在一般项目使用的时候,很多也会使用第三种情况:

默认的配置如下:

image-20240402203615961

直接将接口包写成mapper,然后在里面创建接口以Mapper结尾,然后我们的xml配置文件就写和接口,相同名称。

8、作用域(Scope)

image-20240402204102171

image-20240402204120892

image-20240402204323945

image-20240402204542096

image-20240402204549554

这里的每一个Mapper,就代表一个具体的业务!

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

修改实体类

public class User {
    private int id;
    private String userName;
    private String passWord;
	
    ... 省略其他代码
}

数据库的字段名:

image-20240402204935604

发现不能够和实体类的字段对应上,那么现在我们在运行一下之前的代码:

image-20240402205008547

发现查出来的数据;id 和 passWord 能够查出来,打死你userName查不出来了,

原因是id和数据库是能够对应起来的,然后passWord 虽然大小写不一样,但是里面的单词还是相同所以可以识别,我们再次改变属性名:

public class User {
    private int id;
    private String userName;
    private String passWord1;
    ...........
}

再次测试运行:

image-20240402205217501

分析问题:

我们执行的sql如下:

select * from `user`;
-- 其实可以 == 如下
select `id`, `name`, `password` from `user`;

在mybatis中会通过查出来的字段名取和java实体类的属性取匹配,匹配上了就可以将数据复制给实体类了,但是这边我们修改了实体类,name属性去找userName发现找不到所以就没有成功进行复制,我们可以调整sql让查出来的字段对应到实体类,就可以查出数据了;

<select id="getUserList" resultType="user">
    select `id`, `name` userName, `password` passWord1 from `user`;
</select>

再次查询:

image-20240402210020503

发现数据成功获取,但是这种方式相对来说还是比较麻烦,而且每次查询的数据都要去写一次别名,非常麻烦

然后我们就可以通过mybatis提供的resultMap来进行处理;

首先修改查询sql的返回值类型

<select id="getUserList" resultMap="userResult">
    select * from `user`;
</select>

resultMap就是我们需要返回的数据对象类型,但是这边没有一个叫做 userResult 的东西,所以这个东西就需要我们自己来创建

image-20240402210431464

我们可以创建一个resultMap属性,里面就可以设置一个id值,在我们的select语句的返回对象就可以使用resultMap来进行返回,内容就是我们定义的resultMap的id值;

上面的type就是写当前需要转换后的实体类的名称;例如之前的select的返回值resultType值,就是我们的User类

<resultMap id="userResult" type="user">
</resultMap>

里面的属性

image-20240402211747039

目前就使用id、result就可以

image-20240402211854712

<resultMap id="userResult" type="user">
    <id property="id" column="id" />
    <result property="userName" column="name" />
    <result property="passWord1" column="password" />
</resultMap>

里面主要就是一个对应数据库查出来的字段--一个对应到java实体类名。

测试:

image-20240402212036189

查询成功;

例如现在替换了实体类名称:

public class User {
    private int id;
    private String username111;
    private String password222;
    ......
}

修改xml文件:

<resultMap id="userResult" type="user">
    <id property="id" column="id" />
    <result property="username111" column="name" />
    <result property="password222" column="password" />
</resultMap>

image-20240402212207298

数据同样能查出来;

image-20240402212726500

日志

1、日志工厂

image-20240402213210313

image-20240402213221755

image-20240402213254385

2、 配置STDOUT_LOGGING标准日志输出:

<settings>
    <!-- 配置日志输出  注意 logImpl 大小写不能错 两个值都不能错一个单词,不能多一个空格 -->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

运行查询代码查看输出:

image-20240402214023851

3、配置Log4J日志输出

image-20240403090051841

注意:使用Log4J需要先导入Log4J所依赖的包,不然会报错!

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>

修改配置文件为Log4J输出

<settings>
    <!-- 配置日志输出  注意 logImpl 大小写不能错 两个值都不能错一个单词,不能多一个空格 -->
    <setting name="logImpl" value="Log4J"/>
</settings>

测试运行:

image-20240402215140474

发现报错了!

解释: 这条警告信息来自Log4j日志框架,表示没有为名为org.apache.ibatis.logging的日志记录器找到任何日志追加器(appender)。Log4j用追加器指定日志信息要输出到哪里,比如控制台、文件等。如果没有配置追加器,那么日志信息就不会被输出。

解决方法:

  1. 确保在项目中包含了Log4j的配置文件,如log4j.propertieslog4j.xml
  2. 在配置文件中为org.apache.ibatis.logging logger配置一个或多个追加器,并指定日志级别。

例如,在log4j.properties中添加如下配置:

propertieslog4j.logger.org.apache.ibatis.logging=DEBUG, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.Target=System.outlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

这个配置将为org.apache.ibatis.logging logger设置了输出到控制台的追加器,并将日志级别设置为DEBUG。

请根据你的具体需求调整配置,例如更改日志级别或追加器的类型。如果你不需要日志信息,也可以选择忽略这个警告。

添加一个配置文件

log4j.properties

log4j.rootLogger=DEBUG, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

再次运行:

image-20240403091532526

其他自行了解..........

分页

为什么要分页?

  • 减少数据的处理量

1、使用Limit分页

-- 语法:select * from limit 起始位置, 查多少条
-- 当只有一个参数: select * from limit n;  意味着后面其实是 [0, n]

使用Mybatis实现分页:

先在数据库中多添加几条数据:

image-20240403092650563

  1. 编写接口
// 分页查询用户的数据
List<User> getUserListByPage(Map<String, Object> map);
  1. 编写mapper.xml
<select id="getUserListByPage" resultMap="userResult" parameterType="map">
    select * from `user` limit #{pageStart}, #{pageSize}
</select>
  1. 封装查询数据测试运行:
// 封装查询对象
public List<User> getUserListByPage(int page, int pageSize){
    // 获取链接对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    // 获取mapper
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 封装查询数据
    HashMap<String, Object> map = new HashMap<>();
    // 添加分页起始数据
    map.put("pageStart", (page-1) * pageSize); // 一页有 pageSize 条数据,现在查询第 page 页,就重第 (page-1) * pageSize 条数据开始查
    // 添加每一个显示的数量
    map.put("pageSize", pageSize);

    // 开始查询
    List<User> userListByPage = userMapper.getUserListByPage(map);

    // 输出查询数据
    userListByPage.forEach(System.out::println);

    // 关闭数据库连接
    sqlSession.close();

    return userListByPage;
}

测试代码:

@Test
public void testUserDAO(){
    // 测试调用 查询第 1 页数据, 每一页 3 条数据
    List<User> userListByPage = getUserListByPage(1, 3);
    // 输出数据
    userListByPage.forEach(System.out::println);
}

image-20240403093437918

测试查询第二页:

@Test
public void testUserDAO(){
    // 测试调用 查询第 1 页数据, 每一页 3 条数据
    List<User> userListByPage = getUserListByPage(2, 3);
    // 输出数据
    userListByPage.forEach(System.out::println);
}

image-20240403093500690

2、RowBounds

不在使用sql实现分页了(不推荐使用,效率低,并且不安全,因为要使用sql的selectList方法,并不是通过类class实现)

  1. 接口
// 使用RowBounds实现分页
List<User> getUserListByRowBounds();
  1. xml配置
<select id="getUserListByRowBounds" resultMap="userResult">
    select * from `user`
</select>
  1. 编写测试
@Test
public void testUserDAO(){
    // 创建RowBounds对象
    RowBounds rowBounds = new RowBounds(1, 3);// 这边的意思还是查询第 2 页, 每页 3 条数据

    // 获取链接对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    // 查询数据
    List<User> userList = sqlSession.selectList("com.huaijiuwang.mapper.UserMapper.getUserListByRowBounds", null, rowBounds);
    // 打印数据
    userList.forEach(System.out::println);

    // 关闭链接
    sqlSession.close();
}

image-20240403095415010

使用注解开发

1、增删改查注解

步骤如下:

第一步还是先编写接口

public interface UserMapper {
    List<User> getUserList();
}

接下来就不是去配置mapper.xml文件了,直接在方法上面使用注解@Select

@Select("select * from `user`")
List<User> getUserList();

注解里面就写之前在mapper.xml中写的sql语句

第二步,在配置文件添加

<mappers>
	<mapper class="com.huaijiuwang.mapper.UserMapper" />
</mappers>

在这边就不需要再添加mapper.xml的映射了

第三步,运行测试:

@Test
public void testUserDAO(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    List<User> userList = mapper.getUserList();

    userList.forEach(System.out::println);

    sqlSession.close();
}

image-20240403101824969

数据查询到了,但是这边还是有局限性,不能配置resultMap了,所以数据无法映射

但是可以使用之前的方式修改sql别名:

@Select("select id, name username111, password password222 from `user`")
List<User> getUserList();

image-20240403101948530

完成查询

测试查询有参数的语句,同样可以

@Select("select id, name username111, password password222 from `user` where id = #{id}")
User getUserById(int id);

测试运行:

@Test
public void testUserDAO(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user = mapper.getUserById(1);
    System.out.println(user);

    sqlSession.close();
}

image-20240403102127230

查询成功,其他的增删改同理

  • @Update("sql")
  • @Delete("sql")
  • @Insert("sql")

image-20240403102310553

所以在简单的增删改查的代码可以使用注解方式来实现,但是对于复杂的还是推荐使用xml配置的方式来实现;

2、参数注解

例如一个需要多个参数的sql:

测试之前写法:

@Insert("update user set name = #{username111}, password = #{password222} where id = #{id}")
int update(User user);

修改用户数据测试:

@Test
    public void testUserDAO(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        mapper.update(new User(1, "怀旧1", "1111"));

        List<User> userList = mapper.getUserList();
        userList.forEach(System.out::println);

        sqlSession.close();
    }

image-20240403104624723

修改成功,但是现在还是之前的需求,需要修改到id的数据,所以就需要两个id数据,现在就可以使用注解@Param()注解来实现,修改代码

@Insert("update user set id= #{newId}, name = #{name}, password = #{password} where id = #{oldId}")
int update(@Param("oldId") int oldId, @Param("newId") int newId, @Param("name") String name, @Param("password") String password);

测试代码:

@Test
public void testUserDAO(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    mapper.update(10, 100, "哈哈哈", "33333");

    List<User> userList = mapper.getUserList();
    userList.forEach(System.out::println);

    sqlSession.close();
}

image-20240403104958176

修改成功!注意:当接口方法中存在多个参数就必须使用Param注解

image-20240403105842738

提醒:

在编写sql里面的参数的时候我们一般使用的是 #{} 来进行参数传递,但是还有一种方式 ${} 他们二者的区别是 #{} 是预编译的可以防止sql注入而 ${} 是拼接的方式,不能访sql注入。

Lombok

1、Lombok介绍:

  • Lombok是一个Java库
  • 它通过使用注解来简化Java代码的编写
  • 这个库可以自动插入到编辑器和构建工具中
  • 帮助开发者减少编写重复且繁琐的代码
  • 如getter、setter方法、equals和hashCode方法、构造函数等
  • 使用Lombok,开发者只需在Java类上添加相应的注解,而不需要手动编写相应的代码
  • 在编译过程中,Lombok会根据这些注解自动生成对应的代码,减少代码中的冗余和样板代码,以及简化代码的编写过程

Lombok的使用可以提高代码的可读性、简洁性和可维护性,让开发者能够更专注于业务逻辑的实现。例如,通过@Data注解,Lombok可以在编译时自动生成get、set、equals、hash、toString等方法,避免写大量的代码,减少了代码量,也使代码看起来更加简洁。尤其对于一些对象属性需要改动的时候,每次改动都需要重新生成这些方法,而使用注解则可以避免此问题。

2、Lombok使用:

  1. 导入需要的依赖
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>

使用测试:

之前编写的实体类

public class User {
    private int id;
    private String username111;
    private String password222;


    public User() {
    }

    public User(int id, String username111, String password222) {
        this.id = id;
        this.username111 = username111;
        this.password222 = password222;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername111() {
        return username111;
    }

    public void setUsername111(String username111) {
        this.username111 = username111;
    }

    public String getPassword222() {
        return password222;
    }

    public void setPassword222(String password222) {
        this.password222 = password222;
    }

    public String toString() {
        return "User{id = " + id + ", username111 = " + username111 + ", password222 = " + password222 + "}";
    }
}

image-20240403111509112

现在的情况,加上注解:

image-20240403111621120

自动生成了其这些方法;

其中还有很多其他的注解如下:

3、常见的Lombok基本注解

注解注解作用
@Getter自动生成字段的getter方法
@Setter自动生成字段的setter方法
@ToString自动生成toString方法
@EqualsAndHashCode自动生成equals和hashCode方法
@NoArgsConstructor自动生成无参构造函数
@AllArgsConstructor自动生成包含所有字段的构造函数

4、常见的Lombok组合注解

注解注解作用
@Data组合了@Getter、@Setter、@ToString、@EqualsAndHashCode等注解的功能
@Value组合了@Getter、@ToString、@EqualsAndHashCode等注解的功能

思考:@Data注解和@Value注解有哪些区别?

解答:@Data生成的类是可变的,具有可读写的setter方法,@Value生成的类是不可变的,字段是final的,没有生成setter方法。

5、Lombok日志注解

注解注解作用
@Slf4j自动生成一个名为log的日志记录器

6、Lombok其他注解

注解注解作用
@Builder自动生成Builder模式的构造器方法
@NonNull自动生成非空检查
@Delegate自动生成委托方法
@Cleanup自动释放资源

一般情况下使用最多的是如下的

@Data 
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String username111;
    private String password222;
}

一个所有的,一个无参,一个全参的;

7、Lombok的优缺点

image-20240403112546914

Mybatis 多对一使用

​ 多对一的关系在数据库中十分常见,例如:一个老师教很多学生,从老师的角度来是一对多的关系,而从学生的角度来看就是多对一的关系。

1、数据库表结构创建

执行下面的sql:

CREATE DATABASE `mybatis`;

USE `mybatis`;

CREATE TABLE `teacher` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

insert  into `teacher`(`id`,`name`) values (1,'怀旧老师');

CREATE TABLE `student` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `teacher_id` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `teacher_id` (`teacher_id`),
  CONSTRAINT `student_ibfk_1` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

insert  into `student`(`id`,`name`,`teacher_id`) values (2,'怀旧',1),(3,'小黄',1),(4,'小黑',1),(5,'小美',1),(6,'小甜',1);

现在是在数据库中创建了两张表,分别是学生表,和教师表,里面学生表中对应到教师的id,来构成多对一的连接关系.

2、java创建代码和文件

image-20240403140831328

文件结构如下,编写代码:

StudentMapper、TeacherMapper 为空接口

Student实体类:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

Teacher实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
    private int id;
    private String name;
}

StudentMapper.xml

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.huaijiuwang.mapper.StudentMapper">

</mapper>

TeacherMapper.xml

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.huaijiuwang.mapper.TeacherMapper">

</mapper>

mybatis-config.xml 文件:

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 引入外部配置文件 -->
    <properties resource="db.properties" />

    <!-- 给类取别名 -->
    <typeAliases>
       <package name="com.huaijiuwang.pojo"/>
    </typeAliases>

    <!-- 配置链接环境(可以配置多套)  -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <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文件都需要在下面进行注册,不然不能使用 -->
    <mappers>
        <mapper resource="mapper/StudentMapper.xml" />
        <mapper resource="mapper/TeacherMapper.xml" />
    </mappers>
</configuration>

配置完成,测试基本查询是否能够运行

编写一个接口查询教师数据

// 根据教师id查询教师数据
Teacher getTeacherById(int id);

编写mapper文件

<select id="getTeacherById" parameterType="int" resultType="teacher">
    select * from teacher where id = #{id}
</select>

编写测试:

@Test
public void testUserDAO(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

    Teacher teacher = mapper.getTeacherById(1);

    System.out.println(teacher);

    sqlSession.close();
}

image-20240403142409481

数据查询成功,环境没有问题;

3、实现一对一需求(一个学生对应一个老师)

现在需要查询所有学生的数据,并且包含这个学生对应老师的数据

select * from student s, teacher t where s.teacher_id = t.id

image-20240403143147184

数据查询成功

编写接口

// 查询所有学生数据
List<Student> getStudentList();

编写xml

<select id="getStudentList" resultType="student">
    select * from student s, teacher t where s.teacher_id = t.id
</select>

编写测试:

SqlSession sqlSession = MybatisUtil.getSqlSession();

StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

List<Student> studentList = mapper.getStudentList();

studentList.forEach(System.out::println);

sqlSession.close();

image-20240403143525946

结果中,学生的数据查询出来了,但是教师的数据还是为null,所以就需要修改我们的查询语句了;

在我们的mapper.xml中,返回的结果集中,返回的是Student实体类数据,但是查询出来的是一条一条的数据,没办法将教师的数据封装成一个对象赋值给student的教师属性,所以我们可以尝试新的方式,resultMap

<resultMap id="studentResult" type="student">
    <id property="id" column="id" />
    <result property="name" column="name" />
</resultMap>

<select id="getStudentList" resultMap="studentResult">
    select * from student s, teacher t where s.teacher_id = t.id
</select>

我们知道id可以封装查询出来的一个主键,result可以封装一个字段,我们在看看里面还有其他的什么方法;

image-20240403144009835

里面还有:association 和 collection ;

其实这两个对象就是用来将我们查询出来的数据进行封装处理的属性

association :可以封装一个对象

collection :可以封装一个对象集合

我们这边是需要一个教师对象,所以就可以使用association

在 association 也需要编写 property 用来对应到实体类的属性;

然后在 property 的里面可以在封装一个对象:案例如下

<resultMap id="studentResult" type="student">
    <id property="id" column="id" />
    <result property="name" column="name" />
    <association property="teacher">
        <id property="id" column="id" />
        <result property="name" column="name" />
    </association>
</resultMap>

运行测试:

image-20240403144836291

发现数据查询出来了,但是教师的结果发生了错误!

这边发生这种情况是因为我们学生类和教师类的属性都一样,所以刚好查出来的数据对应上了,所以教师的数据成了学生的数据;

但是我们的sql语句查询出来的结果如下

image-20240403144508228

想要查出来的数据能够对应上,我们可以直接修改查出来的数据的别名,让他有一个自己的标识

select s.id s_id, s.name s_name, t.id t_id, t.name t_name from student s, teacher t where s.teacher_id = t.id

image-20240403145202448

这样我们在修改resultMap

<resultMap id="studentResult" type="student">
    <id property="id" column="s_id" />
    <result property="name" column="s_name" />
    <association property="teacher" javaType="teacher">
        <id property="id" column="t_id" />
        <result property="name" column="t_name" />
    </association>
</resultMap>

<select id="getStudentList" resultMap="studentResult">
    select s.id s_id, s.name s_name, t.id t_id, t.name t_name from student s, teacher t where s.teacher_id = t.id
</select>

运行测试:

image-20240403145322461

发现成功查询出来了数据;

还有一种写法

直接上mapper.xml代码

<resultMap id="studentResult" type="student">
    <id property="id" column="s_id" />
    <result property="name" column="s_name" />
    <association property="teacher" column="teacher_id" javaType="teacher" select="getStudent" />
</resultMap>

<select id="getStudentList" resultMap="studentResult">
    select * from student
</select>

<select id="getStudent" resultType="teacher" parameterType="int">
    select * from teacher where id = #{teacher_id}
</select>

解释:

第一种方式来说,是通过多表查询一次性查出两张表的所有数据,然后直接将数据封装到实体类

第二种方式来说,是通过先查询学生的数据,然后根据查询出来的学生数据中的教师id在去查询一次教师数据,然后在封装到实体类中;可以理解为子查询。

二者比较:第一种方式效率会高一点,第二种方式更利于理解;

4、实现 一对多的处理(一个老师有多个学生)

首先修改实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
    private int id;
    private String name;
    private List<Student> studentList;
}

因为一个老师对应多个学生,所以这边需要创建一个属性用来存这一个老师对应的所有学生

需求:根据教师id查询教师的数据,并查询出这个教师的所有学生数据

编写接口

// 查询所有的教师数据
List<Teacher> getTeacherList();

编写mapper.xml

<select id="getTeacherList" resultType="teacher">
    select * from teacher
</select>

测试代码:

SqlSession sqlSession = MybatisUtil.getSqlSession();
sqlSession.getMapper(TeacherMapper.class).getTeacherList().forEach(System.out::println);
sqlSession.close();

image-20240403151053578

修改mapper.xml,查询多个学生

sql编写测试:

select t.id t_id, t.name t_name, s.id s_id, s.name s_name from teacher t inner join student s on t.id = s.teacher_id

image-20240403151729352

查询结果如下,现在多个教师数据对应到了很多位学生数据了

我们编写xml

<resultMap id="teacherResult" type="teacher">
    <id property="id" column="t_id" />
    <result property="name" column="t_name" />
    <collection property="studentList" ofType="student">
        <id property="id" column="s_id" />
        <result property="name" column="s_name" />
    </collection>
</resultMap>

<select id="getTeacherList" resultMap="teacherResult">
    select t.id t_id, t.name t_name, s.id s_id, s.name s_name from teacher t inner join student s on t.id = s.teacher_id
</select>

测试运行:

SqlSession sqlSession = MybatisUtil.getSqlSession();

for (Teacher teacher : sqlSession.getMapper(TeacherMapper.class).getTeacherList()) {
    System.out.println(teacher);
    teacher.getStudentList().forEach(System.out::println);
}

sqlSession.close();

image-20240403152340865

成功查询出学生集合:注意,这边的collection属性,查询出俩的list数据需要写ofType类型,不然会报错。。。。

按照子查询的方式在查一次

修改mapper.xml

<resultMap id="teacherResult" type="teacher">
    <id property="id" column="t_id" />
    <result property="name" column="t_name" />
    <collection property="studentList" column="id" ofType="student" select="getStudentsByTeacherId" />
</resultMap>

<select id="getTeacherList" resultMap="teacherResult">
    select * from teacher
</select>

<select id="getStudentsByTeacherId" resultType="student" parameterType="int">
    select * from student where teacher_id = #{id}
</select>

image-20240403153259394

同样查询成功!

5、总结

  1. 关联 - association 【多对一】
  2. 集合 - collection 【一对多】
  3. javaType & ofType
    • javaType 用来指定实体类中的属性类型
    • ofType用来自定映射到List或者集合中的实体类类型,泛型中的约束类型!

image-20240403154130198

注意点:

image-20240403154207194

动态SQL

1、什么是动态SQL

​ MyBatis中的动态SQL是一种可以根据不同条件生成不同SQL语句的技术。它允许我们在映射文件中编写灵活的SQL语句,以便根据参数的不同情况来动态生成SQL语句。这种灵活性使得我们能够根据应用程序的需求来构建动态的查询语句。

2、动态SQL的作用

动态SQL是根据不同条件和需求,动态生成SQL语句的一种技术。它的作用主要有以下几点:

  1. 条件灵活:使用动态SQL可以根据不同的条件生成不同的SQL语句,使得查询、更新或删除数据时能够根据具体情况进行灵活的处理。

  2. 查询优化:有时候在编写静态SQL语句时难以预料到查询条件的变化,而使用动态SQL可以根据运行时的条件动态调整查询语句,从而更好地适应实际情况,提高查询性能。

  3. 动态表名和字段名:有时候需要根据不同的场景来操作不同的表或字段,这时候就可以利用动态SQL来动态构建表名和字段名,实现灵活性和扩展性。

  4. 防止SQL注入:通过使用参数化查询或者绑定变量的方式来构建动态SQL,可以有效防止SQL注入攻击,提升系统的安全性。

3、动态SQL的常用标签

常用标签作用
if根据指定的条件判断是否包含某部分SQL代码,使得SQL语句在运行时更具灵活性。
where生成动态的WHERE子句,只有满足条件时才包含WHERE子句,避免不必要的WHERE关键字。
choose根据不同的条件选择执行不同的SQL片段,实现类似于switch-case语句的功能。
foreach对集合进行循环,并在SQL语句中使用循环的结果,可以用于动态构建IN或VALUES子句。
set生成动态的SET子句,只有满足条件时才包含SET子句,用于动态更新表中的字段。
trim对SQL语句进行修剪和重组,去掉多余的AND或OR等,以便根据不同的条件动态生成合适的SQL语句。

4、if标签的使用

查询一条学生数据(需求:传入一个学生信息,如果传入信息不为空,就查询对应字段比对的数据)

编写接口:

// 查询学生数据
List<Student> getStudentListByStudent(Student student);

编写mapper.xml

<select id="getStudentListByStudent" resultType="student" parameterType="student">
    select * from student
    where 1=1
    <if test="id != 0">
        and id = #{id}
    </if>
    <if test="name != null and name != ''">
        and name like concat('%', #{name}, '%')
    </if>
</select>

代码中where条件后面更上了一个 1 = 1,是为了避免后面的if里面要是成立了,但是前面没有数据,后面直接and语句会导致报错(但是这个问题后面可以解决)

这边是如果输入的数id不为0就根据id进行查询,如何用户名不为null或者空,就根据用户名进行模糊查询;

运行测试:

SqlSession sqlSession = MybatisUtil.getSqlSession();

Student student = new Student();

// 没有数据说明查询所有用户数据
sqlSession.getMapper(StudentMapper.class).getStudentListByStudent(student).forEach(System.out::println);

sqlSession.close();

image-20240403163134403

Student student = new Student();
// 设置id值,查询
student.setId(1);

sqlSession.getMapper(StudentMapper.class).getStudentListByStudent(student).forEach(System.out::println);

image-20240403163147313

Student student = new Student();
// 设置id值,查询
student.setId(2);

sqlSession.getMapper(StudentMapper.class).getStudentListByStudent(student).forEach(System.out::println);

image-20240403163159425

Student student = new Student();
// 设置id值,查询
student.setName("小");

sqlSession.getMapper(StudentMapper.class).getStudentListByStudent(student).forEach(System.out::println);

image-20240403163323655

Student student = new Student();
// 设置id值,查询
student.setName("旧");

sqlSession.getMapper(StudentMapper.class).getStudentListByStudent(student).forEach(System.out::println);

image-20240403163348953

测试很成功!

其他标签学习可以参考网站:网页链接

5、SQL片段

image-20240403165106100

<sql id="publicSql">
    <if test="id != 0">
        and id = #{id}
    </if>
    <if test="name != null and name != ''">
        and name like concat('%', #{name}, '%')
    </if>
</sql>
<select id="getStudentListByStudent" resultType="student" parameterType="student">
    select * from student
    where 1=1
    <include refid="publicSql" />
</select>
SqlSession sqlSession = MybatisUtil.getSqlSession();

Student student = new Student();
// 设置id值,查询
student.setName("旧");

sqlSession.getMapper(StudentMapper.class).getStudentListByStudent(student).forEach(System.out::println);

sqlSession.close();

image-20240403165206530

运行同样没问题

image-20240403165454379

image-20240403165851714

缓存

1、简介

image-20240405120000152

image-20240405120512367

2、Mybatis 缓存

image-20240405120808848

3、一级缓存

image-20240405131558393

一级缓存使用测试:

创建根据学生id查询学生数据:

// 根据学生id查询学生数据
Student getStudentById(@Param("id") int id);
<select id="getStudentById" resultType="student" parameterType="int">
    select * from student where id = #{id}
</select>

使用测试:

SqlSession sqlSession = MybatisUtil.getSqlSession();

Student student1 = sqlSession.getMapper(StudentMapper.class).getStudentById(2);

System.out.println(student1);

sqlSession.close();

image-20240405131915259

测试默认开启的一级缓存:

SqlSession sqlSession = MybatisUtil.getSqlSession();

Student student1 = sqlSession.getMapper(StudentMapper.class).getStudentById(2);
System.out.println(student1);

Student student2 = sqlSession.getMapper(StudentMapper.class).getStudentById(2);
System.out.println(student2);

System.out.println(student1 == student2);

sqlSession.close();

image-20240405132143012

当在查询的途中,有了增删改操作,就会自动取刷新缓存:

// 修改学生数据
int updateStudentById(Student student);
<update id="updateStudentById" parameterType="student">
    update student set name = #{name} where id = #{id}
</update>
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

Student student1 = mapper.getStudentById(2);
System.out.println(student1);

mapper.updateStudentById(new Student(2, "啊啊啊", new Teacher()));

Student student2 = mapper.getStudentById(2);
System.out.println(student2);

System.out.println(student1 == student2);

sqlSession.close();

image-20240405132746755

image-20240405132909720

测试可能遇到的问题:

SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

Student student1 = mapper.getStudentById(2);
student1.setName("aaaa");
System.out.println(student1);

Student student2 = mapper.getStudentById(2);
System.out.println(student2);


sqlSession.close();

image-20240405132958413

上面因为有缓存的存在,所以第一次获取数据后,数据被修改,第二次再次获取数据,但是查出来的数据是被修改后的数据,所以这时候在使用的时候就可能会出现bug,现在就需要使用手动关闭缓存来实现数据正常(clearCache 方法)。

SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

Student student1 = mapper.getStudentById(2);
student1.setName("aaaa");
System.out.println(student1);

sqlSession.clearCache();

Student student2 = mapper.getStudentById(2);
System.out.println(student2);

sqlSession.close();

image-20240405133149211

手动关闭缓存后,数据正常。

image-20240405133338278

4、二级缓存

image-20240405133504944

使用步骤:

  1. 开启全局缓存

image-20240405133654310

<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
  1. 在Mapper.xml文件中配置当前namespace中开启二级缓存

image-20240405133952520

image-20240405134001068

<mapper namespace="com.huaijiuwang.mapper.StudentMapper">
    <!-- 配置可以使用二级缓存 -->
    <cache />

自定义设置参数:

<!-- 配置可以使用二级缓存 -->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" />
  1. 使用测试:

还是用之前的例子,这次查询一次数据就关闭sqlSession然后在开一个,重新查询:

SqlSession sqlSession1 = MybatisUtil.getSqlSession();
Student student1 = sqlSession1.getMapper(StudentMapper.class).getStudentById(2);
sqlSession1.close();

SqlSession sqlSession2 = MybatisUtil.getSqlSession();
Student student2 = sqlSession2.getMapper(StudentMapper.class).getStudentById(2);

System.out.println(student1);
System.out.println(student2);
System.out.println(student1 == student2);

image-20240405134502976

通过结果可以看出来,当前还是只查询了一次数据库,并且两次获取的数据是相同的。

关闭二级缓存在测试一下:

image-20240405134541250

image-20240405134559371

关闭后,就查询了两次数据库;

清理缓存的方式和一级缓存一模一样,并且也是数据被增删改才回刷新缓存;

问题:

image-20240405134859115

关闭其他配置:

image-20240405134912014

运行直接报错,原因是当前的实体类没有做序列号,数据无法从缓存取出后直接编译赋值为新的对象中;

解决办法,让实体类实现序列号接口即可;

public class Student implements Serializable {

image-20240405135155523

实现序列号接口后,数据成功返回,并且查询数据库还是只进行了一次,但是数据对象不同了,这样就解决了缓存查询数据,导致第二次查询数据不一致的问题;

image-20240405135612093

5、缓存的原理图

image-20240405140135698

6、自定义缓存

Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存;

要在程序中使用ehcache,先要导包!

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

在mapper中指定使用我们的ehcache缓存实现!

<!--在当前Mapper.xml中使用二级缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

在Resource目录下新建ehcache.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>

    <defaultCache
                  eternal="false"
                  maxElementsInMemory="10000"
                  overflowToDisk="false"
                  diskPersistent="false"
                  timeToIdleSeconds="1800"
                  timeToLiveSeconds="259200"
                  memoryStoreEvictionPolicy="LRU"/>

    <cache
           name="cloud_user"
           eternal="false"
           maxElementsInMemory="5000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           memoryStoreEvictionPolicy="LRU"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->

</ehcache>

注意:

  • 了解即可()一般情况是不会用的
  • 主流还是使用Redis做缓存
  • 平台作者:怀旧(联系作者)
  • QQ:444915368
  • 邮箱:444915368@qq.com
  • 电话:17623747368
  • 评论

    登录后才可以进行评论哦!

    回到顶部 留言