Java-Mybatis 学习笔记


工作区

启航

中文文档:https://mybatis.org/mybatis-3/zh/index.html

github地址:https://github.com/mybatis/mybatis-3

<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.6</version>
    </dependency>
    <!-- junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

什么是MyBatis

  • MyBatis 是一款优秀的持久层框架
  • 它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码
  • 以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
  • 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis
  • 2013年11月迁移到Github。

持久化

  • 数据的持久化:将程序数据再持久状态瞬时状态转化的过程
    • 持久状态:JDBC,IO文件保存
    • 瞬时状态:内存中的数据
    • 是一个概念
  • 持久化用途
    • 部分对象不能丢掉
    • 内存太贵了

持久层

Dao层,Service层,Controller层.

  • 完成持久化工作的代码块
  • 名词

优点

  • 方便,帮助开发者将数据库存入到数据库中
  • 方便JDBC代码,简化,是框架,自动化。
  • 解除sql与代码的耦合

Hello Mybatis

  • mysql 5.7
  • 编写工具类

2.1、新建数据库

CREATE DATABASE `mybatis`;

USE `mybatis`;

CREATE TABLE `user` (
	`id` INT(20) 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, 'hui', '123456'),
(2, 'hui2', '123456'),
(3, 'hui3', '123456')
  • 在打开来的面板中输入用户名和密码连接本地数据库
  • 遇到报错需要调整UTC协调时

2.2、mybatis 核心配置

  • 数据库链接需要加上:useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8,不然会乱码
  • 文件:resources->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>
  <environments default="development">
    <!-- 可以配置多套环境 -->
    <environment id="development">
      <!--事务管理-->
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <!-- &amp;表示转义后的“&” 需要设置编码,不然会乱码 -->
        <property name="url" value="jdbc:mysql://127.0.0.1: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>

2.3、编写工具类

  • 需要读取配置文件,需要封装起来
  • 手册复制下来,需要使用try catch抛出io错误
  • 文件:com->hui->utils->MybatisUtils
// SqlSessionFactory 获得 SqlSession
// SqlSession 包含了所有sql执行语句
public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        // 获取SqlSessionFactory 对象
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /// 获得SqlSession实例
    public static SqlSession getSqlSession() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }

}

2.4、编写代码

  • 实体类
  • 文件com->hui->pojo->User
package com.hui.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接口
  • 文件com->hui->dao->UserDao
// 之后会使用Mapper,Dao 等价于 Mapper
public interface UserDao {
    List<User> getUserList();
}
  • 接口实现类:由原来的UserDaoImpl转变为一个Mapper配置文件
    • 命名空间:为对应接口类 全限定名
    • 返回类型记住两个:resultType(Type:类型)、resultMap(Map:集合)
      • 若返回值不是基本类型,需要用resultType指定
    • 文件com->hui->dao->UserMapper.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">

<!--绑定一个dao/mapper接口-->
<mapper namespace="com.hui.dao.UserDao">
    <!-- 查询语句 返回类型参数选项:Type:类型 Map:集合 -->
    <!-- 需要填写全限定命名 -->
    <select id="getUserList" resultType="com.hui.pojo.User">
        select * from mybatis.user;
    </select>
</mapper>
  • 面向接口编程,xml文件是interface的实现类
  • 只需要在测试类中获得到类就可以了

2.5、测试类

这里会报很多错误!!!!!!

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

  • 每一个mapper都需要在配置文件中注册(resources->mybaits-config.xml
    • 注意这里的核心配置文件路径用/分开
<!-- 核心配置文件  -->
<configuration>
   
    ...
   
  <!-- 每一个Mapper.xml 需要在Mybatis 核心配置文件中注册 -->
  <mappers>
    <mapper resource="com/hui/dao/UserMapper.xml" />
  </mappers>
</configuration>

接着会得到初始化失败,找不到文件的错误

java.lang.ExceptionInInitializerError

Cause: java.io.IOException: Could not find resource com/hui/dao/UserMapper.xml

  • 由于maven约定大于配置,遇到写的配置文件可能出现无法生效问题,在根下的pom.xml中添加以下配置
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>

        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
  • 接着,清理下maven项目,如果允许还报 Could not find resource,就重启idea

可能会得到报错:

Cause: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: 1 字节的 UTF-8 序列的字节 1 无效。

需要将该文件头的(我是UserMpper.xmlmybatis-config.xml需要更改)

<?xml version="1.0" encoding="UTF-8" ?>

改为

<?xml version="1.0" encoding="UTF8" ?>

测试类代码

import com.hui.utils.MybatisUtils;

public class UserDaoTest {
    @Test
    public void test(){
        // 1. 获得sqlSession 对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        // 2. 执行SQL
        // 通过该class的对象获得mapper
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> userList = userDao.getUserList();

        for (User user : userList) {
            System.out.println(user);
        }
        // 关闭连接
        sqlSession.close();
    }
}

2.6、本章总结

  1. 必须指定命名空间
  2. 命名必须用全限定命名,不要使用短名称
<!--绑定一个dao/mapper接口-->
<mapper namespace="com.hui.dao.UserDao">

三个核心类

  • SqlSessionFactoryBuilder
    • 一旦创建就不需要它了,返回SqlSessionFactory
    • 工具类
  • SqlSessionFactory
    • 一旦创建就一直存在,除非close
    • 此类可以创建sqlSession实例
  • sqlSession
    • 执行sql语句

创建顺序

  1. (不变)编写工具类utils
  2. 在 resouces 编写配置文件mybatis-config.xml
  3. (不变)编写实体类,对应数据库pojo->数据库名
  4. 编写接口类(本章)Dao->userDao.interfacce,后续就叫userMapper.interface
  5. 编写接口实现类,这里用xml来实现接口Dao->UserMapper.xml
  6. maven 配置下在build时加载xml文件大项目下的prm.xml

CRUD

顾名思义,增删改查

3.1、命名空间

<!--绑定一个dao/mapper接口-->
<mapper namespace="com.hui.dao.UserDao">
    <!-- 查询语句 返回类型参数选项:Type:类型 Map:集合 -->
    <!-- 需要填写全限定命名 -->
    <select id="getUserList" resultType="com.hui.pojo.User" parameterType="">
        select * from mybatis.user;
    </select>
    <!-- 根据id查询用户 -->
    <select id="getUserById" parameterType="int" resultType="com.hui.pojo.User">
        select * from mybatis.user where id = #{id}
    </select>
</mapper>
  • namespace中包名要和dao/mapper接口 一致

Select 语句

  • id:对应namespace中的方法
  • resultType:sql语句执行的返回值
  • parameterType:参数类型

idea 技巧

3.2、增删改查写法

  • 接下来的操作,就只需要在接口、UserMapper.xml、测试类中编写就行了
  • UserMapper.xml中新增增删改的标签,等于以前在impl实现类中的代码
  • 接着写测试类,只需要注重业务操作,
  • 开始吧!(不再更改实体类和工具类情况)

3.3.1、接口

  • 比如增删改的接口
  • 如果需要根据id找的话,输入参数就使用int id
  • 如果需要更新数据操作,就传入对象User user
package com.hui.dao;

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

// 之后会使用Mapper,Dao 等价于 Mapper
public interface UserMapper {
    // 获得全部用户
    List<User> getUserList();
    // 根据id获得用户
    User getUserById(int id);
    // 插入用户,传入一个对象
    int addUser(User user);
    // 修改用户
    int updateUser(User user);
    // 删除用户
    int delUser(int id);
}

3.2.2、实现接口配置文件

  • resultType=”com.hui.pojo.User” :返回的类型
  • id:要与接口的名字一致,绑定接口
  • update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}:变量用#{变量名}来定义,该变量来自实体类定义的变量(有参)
    • 同时,实体类定义的变量要跟数据库字段一一对应
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--绑定一个dao/mapper接口-->
<mapper namespace="com.hui.dao.UserMapper">
    <!-- 查询语句 返回类型参数选项:Type:类型 Map:集合 -->
    <!-- 需要填写全限定命名 -->
    <select id="getUserList" resultType="com.hui.pojo.User" >
        select * from mybatis.user;
    </select>

    <!-- 根据id查询用户 -->
    <select id="getUserById" parameterType="int" resultType="com.hui.pojo.User">
        select * from mybatis.user where id = #{id}
    </select>

    <!-- 插入一个用户 -->
    <insert id="addUser" parameterType="com.hui.pojo.User">
        insert into mybatis.user (id, name, pwd) VALUES (#{id},#{name},#{pwd})
    </insert>

    <!--更新-->
    <update id="updateUser" parameterType="com.hui.pojo.User">
        update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
    </update>

    <!--删除用户 int 不写类型-->
    <delete id="delUser" >
        delete from mybatis.user where id=#{4}
    </delete>
</mapper>

3.2.2、测试类编写

固定写法

  1. 获得sqlSession对象
    • SqlSession sqlSession = MybatisUtils.getSqlSession()
  2. 通过class对象得到mapper
    • UserMapper userMapper = sqlSession.getMapper(UserMapper.class)
  3. 执行sql语句
    • userMapper.方法
  4. (提交事务 若有增改删操作)
    • sqlSession.commit()
  5. 关闭连接
    • sqlSession.close()
package com.hui.dao;

import com.hui.pojo.User;
import com.hui.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/**
 * @author Hui3c
 * @program: demo3
 * @description: 测试类
 * @date 2021-03-19 17:36:28
 */
public class UserMapperTest {
    @Test
    public void test(){
        // 1. 获得sqlSession 对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        // 2. 执行SQL
        // 通过该class的对象获得mapper
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.getUserList();

        for (User user : userList) {
            System.out.println(user);
        }
        // 关闭连接
        sqlSession.close();
    }
    @Test
    public void getUserById() {
        // 获得sqlSession 对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        // 通过sqlSession获得接口(接口的class对象)
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 调用方法就可以了
        User userById = mapper.getUserById(1);
        System.out.println(userById);

        // 关闭连接
        sqlSession.close();
    }
    @Test
    public void addUser() {
        // 获得sqlSession 对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

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

        // 这里通过无参
        int hui4c = mapper.addUser(new User(4, "hui4c", "`1221`"));
        System.out.println(hui4c);

        // 所有的增删改查都需要提交事务
        sqlSession.commit();
        // 关闭连接
        sqlSession.close();
    }

    @Test
    public void updateUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 要更新的数据
        int i = mapper.updateUser(new User(4, "4chui", "12345"));
        System.out.println(i);

        // 提交事务
        sqlSession.commit();
        sqlSession.close();

    }

    @Test
    public void delUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        int i = mapper.delUser(4);
        System.out.println(i);
        
        sqlSession.commit();
        sqlSession.close();
    }
}

3.3、Map类型操作数据库

  • 实体类或数据库中字段参数过多,考虑使用Map。不然要把全部字段写下来

例子

// 接口 :测试Map
int addUser2(Map<String,Object> map);

实现配置

<!-- 也不需要一一对应, 传递Map 的key。这里返回int类型:基本类型不需要 resultType-->
<insert id="addUser2" parameterType="map">
    insert into mybatis.user (id, pwd) values (#{userid}, #{passWord})
</insert>

测试类

@Test
public void mapAddUser() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    // 新建map对象
    Map<String, Object> map =  new HashMap<String, Object>();

    map.put("passWord", "123123");
    map.put("userid", 6);

    // 接口中接收map类型
    mapper.addUser2(map);

    sqlSession.commit();
    sqlSession.close();
}

3.4、模糊查询和sql注入问题

接口类

// Map 模糊查询
List<User> getUserLike(String value);

UserMapper.xml

<!-- 模糊查询 -->
<select id="getUserLike" parameterType="map" resultType="com.hui.pojo.User">
    select * from mybatis.user where name like #{value};
</select>

测试类

// 模糊查询
@Test
public void getUserLike() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
	
    // 安全的传参方式,在这里用拼接的方式传入值
    List<User> res = mapper.getUserLike("%hui%");
    for (User re : res) {
        System.out.println(re);
    }

    sqlSession.commit();
    sqlSession.close();
}

产生sql注入情况!!!!!

# !!!!危险!!!!
select * from mybatis.user where name like "%"#{value}"%";
List<User> res = mapper.getUserLike("hui");

请不要在实现类配置文件里面拼接通配符 %

应该在使用时对用户传入参数拼接

String input = "hui";
List<User> res = mapper.getUserLike("%"+input+"%");

环境配置

4.1、核心配置文件

核心配置文件mybatis-config.xml

  • 该配置文件包含了会影响mybatis行为设置的属性和信息
  • 画圈为需要掌握内容

properties(属性)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

4.2、配置多套环境

  • 更改原有的配置,使得数据库用户名,密码等方式能通过配置文件读取
  • environments内可以配置多套环境
  • environments内添加default属性指定哪套环境生效
  • Mybatis默认事务管理器是JDBC,连接池:POOLED
<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://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </dataSource>
    </environment>

    <environment id="test">
        ...
    </environment>
</environments>

4.3、引用配置文件

  • 通过properties属性来引用配置文件
  • 引入配置文件
<configuration>
  <properties resource="db.properties" />
</configuration>
  • 创建 resources->db.properties
    • 注意:这里将原来的&amp;改成了&
username=root
url=jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
password=123456
driver=com.mysql.jdbc.Driver
  • 使用变量来引入
<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>
  • 配置文件优先于当前配置(引入后也能定义变量)
<properties resource="db.properties" >
    <property name="username" value="654321"/>
</properties>

4.4、类型别名

方式一

  • java类型设置一个短的名字,用于减少完全限定名的冗余
    • 注意:设置别名的应该为实体类
<typeAliases>
    <typeAlias type="com.hui.pojo.User" alias="User"/>
</typeAliases>
  • 实现配置修改前后使用对比
<select id="getUserList" resultType="com.hui.pojo.User" >
    select * from mybatis.user;
</select>
<!-- 修改后 -->
<select id="getUserList" resultType="User" >
    select * from mybatis.user;
</select>

方式二:实体类较多使用

扫描包后,默认的别名是类的名字首字母小写

<typeAliases>
    <package name="com.hui.pojo"/>
</typeAliases>
<select id="getUserList" resultType="user" >
  • 可以在实体类前使用注解@Alias()来指定名字
import org.apache.ibatis.type.Alias;

@Alias("bieming")
public class User {}
  • 使用效果如下
<select id="getUserList" resultType="bieming" >

优先级:TypeAliais > 注解 > 限定包名

4.5、其他配置

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件),推荐几个
    • mybatis-generator-core
    • mybatis-plus
    • 通用mapper

4.6、映射器(mappers)

绑定Mapper文件

<mappers>
  <mapper resource="com/hui/dao/UserMapper.xml"/>
</mappers>
  • 也可以使用通配符进行匹配*.xml
<mapper resource="com/hui/dao/*.xml"/>

绑定对应的clas s文件

<mappers>
    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mapper class="com.hui.dao.UserMapper"/>
</mappers>

注意:

  • 接口和Mapper 必须同名
  • 接口和Mapper 必须同包

扫描指定包

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

注意:

  • 接口和Mapper 必须同名
  • 接口和Mapper 必须同包

生命周期和作用域

生命周期和作用域,是至关重要的,错误使用很可能产生并发问题

5.1、SqlSessionFactoryBuilder

  • 一旦创建了,就不再需要它了。
  • 该实例最佳作用域是方法作用域(也就是局部方法变量)。

5.2、SqlSessionFactory

  • 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
  • 在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。
  • 因此的最佳作用域是应用作用域
  • 最简单的就是使用单例模式或者静态单例模式。

5.3、SqlSession

  • 连接到连接池的一个请求。
  • 实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 用完后因立即关闭,否则资源被占用
    • sqlSession.close()

结果映射ResultMap

6.1、问题产生

可用的解决方法

  • 不适用于连多表查询,只能适用于简单的的查询
  • 使用数据select 语句中的as 别名指定
<select id="getUserListById" resultType="User" parameterType="int">
    select id,name,pwd as password from mybatis.user where id=#{id};
</select>

6.2、简单的ResultMap使用

ResultMap:结果集映射

  • 使用操作属性resultMap指定返回值为ResultMap
  • 定义ResultMap合集
  • 使用result标签
    • property:属性,实体类中与数据库字段不一致的名字
    • column:字段名,对应数据库的字段
<resultMap id="UserType1" type="User">
    <result property="password" column="pwd" />
</resultMap>

<select id="getUserListById" resultMap="UserType1">
    select id,name,pwd from mybatis.user where id=#{id};
</select>

6.3、ResultMap知识点

  • resultMap 元素是 MyBatis 中最重要最强大的元素
  • 对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了,使用resultMap->result来描述这种简单的关系
  • 如果这个世界总是这么简单就好了。

日志

7.1、日志工厂

程序出现异常需要排错。日志是最好的助手。待补充

在设置中开启

logImpl:日志输出类型

  • SLF4J
  • LOG4J 【常用】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING 【常用】:自带的日志输出
  • NO_LOGGING

Mybatis-config.xml开启并指定:

<settings>
	<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

7.2、LOG4J

  • 是Apache的一个开源项目
  • 可以控制日志信息输送的目的地是控制台文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
  • 可以控制每一条日志的输出格式
  • 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
  1. 导入LOG4J

数据库分页

8.1、传统的方式分页

# 一页两个,显示第二页
SELECT * FROM mybatis.`user` LIMIT 2,2;

# 一页3各,显示第一页
SELECT * FROM mybatis.`user` LIMIT 3,1;

8.2、使用limit进行分页

编写分页接口

List<User> getUserListLimit(Map<String, Integer> map);

编写实现配置文件

<!-- 绑定resultMap值 -->
<resultMap id="UserType1" type="User">
    <result property="password" column="pwd" />
</resultMap>
<!--分页查询-->
<select id="getUserListLimit" resultMap="UserType1">
    select * from mybatis.user limit #{startIndex},#{page}
</select>

编写测试类

public void getUserListLimit() {
    SqlSession sqlSession = MybatisUtils.sqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    // 创建新的map类型用于传参
    HashMap<String, Integer> map = new HashMap<>();
    map.put("startIndex", 2);
    map.put("page", 2);

    List<User> userListLimit = mapper.getUserListLimit(map);
    for (User user : userListLimit) {
        System.out.println(user);
    }
    
    sqlSession.close();
}

8.3、使用 RowBounds 分页【了解】

接口

List<User> getUserByRowBounds();

实现类配置文件

  • 查询时直接select 全部
<!--分页查询-->
<select id="getUserByRowBounds" resultMap="UserType1">
    select * from mybatis.user
</select>

测试类

  • offset:一页几个 limit:第几页
@Test
public void getUserByRowBoundsTest(){
    SqlSession sqlSession = MybatisUtils.sqlSession();
    // offset:一页几个 limit:第几页
    RowBounds rowBounds = new RowBounds(2, 2);

    List<User> userList = sqlSession.selectList("com.hui.dao.UserMapper.getUserByRowBounds", null, rowBounds);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

使用注解进行开发

注解开发缺陷:只能写简单的!复杂的还是需要配置文件

9.1、增删改查

查询所有

  • 接口上注解
@Select("select * from user")
List<User> getUsers();

增加用户

  • password为实体类中定义的变量
  • pwd为数据库字段
@Insert("insert into user(id,name,pwd) values(#{id},#{name},#{password})")
int addUser(User user);
  • 测试类
// 注解添加用户
public void anAddUser(){
    SqlSession sqlSession = MybatisUtils.sqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.addUser(new User(7,"hui7c","123456"));

    sqlSession.commit();
    sqlSession.close();
}

更新用户

@Update("update user set name=#{name},pwd=#{password} where id = #{id}")
int updateUser(User user);
  • 测试类
mapper.updateUser(new User(7,"7chui","123456"));

删除用户

  • 你也可以设置别名,这里uid为别名
@Delete("delete from user where id = #{uid}")
int delUser(@Param("uid") int id);
  • 测试类
mapper.delUser(7);

9.2、关于@Param

  • 基本类型的参数或者Sting类型,都需要加上
  • 引用类型不需要加
  • 若只有一个基本类型,可以忽略【但建议都加上】
  • SQL中直接引用的就是@Param中设置的属性名

Lombok

极其偷懒的一个注解开发,使用后能够用一个注解来生成实体类的getset,构造,无参

  1. 导入包
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.6</version>
</dependency>
  1. idea 下载插件,插件搜索lombok安装
  2. 使用
    • @Data:包含getsettostringhashcodeequals
    • @AllArgsConstructor:有参构造
    • @NoArgsConstructor:无参构造
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String password;
}

一对多与多对一

11.1、多对一查询

环境搭建

  1. 数据库创建
CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO teacher(`id`, `name`) VALUES (1, '老师'); 

CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
  1. 实体类,绑定接口
  • 创建实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;

    private Teacher teacher;
    //private int tid;

}

// ======================================
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
}
  • 注册Mapper
    • 设置别名,基础配置,注册mapper
<?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" />
	name="logImpl" value="STDOUT_LOGGING"/>
  </settings>

	<typeAliases>
    <typeAlias type="com.hui.pojo.Teacher" alias="Teach" />
    <typeAlias type="com.hui.pojo.Student" alias="Student" />
  </typeAliases>
  <environments default="development">
   ...
  </environments>
  <mappers>
    <mapper resource="com/hui/dao/TeachMapper.xml"/>
    <mapper resource="com/hui/dao/StudentMapper.xml"/>
  </mappers>
</configuration>
  • 新建查询
Teacher getTeacher(int id);
<mapper namespace="com.hui.dao.TeachMapper">
    <select id="getTeacher" resultType="com.hui.pojo.Teacher">
        select * from mybatis.teacher where id = #{id}
    </select>
</mapper>
  • 创建测试类
@Test
public void getTeacher() {
    SqlSession sqlSession = MybatisUtils.sqlSession();
    TeachMapper mapper = sqlSession.getMapper(TeachMapper.class);

    Teacher teacher = mapper.getTeacher(1);
    System.out.println(teacher);

    sqlSession.close();
}

目的:查询所有学生,以及对应老师的信息!

传统sql 查询

SELECT s.id,s.name,t.name FROM `teacher` t, `student` s WHERE s.tid = t.id

子查询

按结果嵌套护理

  • 实体类不变
  • 查询思路:
    1. 查询所有学生
    2. 根据查到学生的tid找到对应的老师
  • 复杂属性标签处理:
    • association:对象, collection:集合
  • associationcollection 下指定返回类型
    • 指定属性的类型:javaType
    • 目标为集合,表示泛型。使用:ofType

StudentMapper.xml如下:

<mapper namespace="com.hui.dao.StudentMapper">
    <select id="getStudent" resultMap="StudentTeacher">
        select * from mybatis.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="Teach" select="getTeacher" />
    </resultMap>

    <select id="getTeacher" resultType="Teach">
        select * from mybatis.teacher where id = #{id}
    </select>
</mapper>

备注一下:

  • column 写的是当前数据库的字段
  • property 写的是对应实体类的属性
  • javaType 写的是实体类的类型,这里用了别名
  • association表示对对象进行处理

同时Teacher内又有id,和name,最终查询返回结果如下

Student(id=1, name=小明, teacher=Teacher(id=1, name=老师))
Student(id=2, name=小红, teacher=Teacher(id=1, name=老师))
Student(id=3, name=小张, teacher=Teacher(id=1, name=老师))
Student(id=4, name=小李, teacher=Teacher(id=1, name=老师))
Student(id=5, name=小王, teacher=Teacher(id=1, name=老师))

关联查询

  • 按照结果嵌套处理
<mapper namespace="com.hui.dao.StudentMapper">
    <select id="getStudent" resultMap="StudentTeacher">
        SELECT s.id sid,s.name sname,t.name tname
        FROM mybatis.teacher t, mybatis.student s
        WHERE s.tid = t.id
    </select>
    <resultMap id="StudentTeacher" type="Student" >
        <result property="id" column="sid" />
        <result property="name" column="sname" />
        <!-- 在java 类内叫teacher,结果就应该是实体类的那几个属性-->
        <association property="teacher" javaType="Teach">
            <!-- 继续处理Teach内的属性,与数据库对应-->
            <result property="name" column="tname" />
        </association>
    </resultMap>
</mapper>
  • column :写的是查询时设置的别名

查询结果:

  • 因为在这次演示中, select 中没有查询 teacherid,所以id为0

Student(id=1, name=小明, teacher=Teacher(id=0, name=老师))
Student(id=2, name=小红, teacher=Teacher(id=0, name=老师))
Student(id=3, name=小张, teacher=Teacher(id=0, name=老师))
Student(id=4, name=小李, teacher=Teacher(id=0, name=老师))
Student(id=5, name=小王, teacher=Teacher(id=0, name=老师))

11.2、一对多

  • 一个老师拥有多个学生
  • 在老师的视角:就是一对多的关系
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
    // 旗下的学生
    private List<Student> students;
}
// =========================
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;
    private int tid;
}

Sql执行语句:

SELECT s.id sid, s.`name` sname, t.`name` tname, t.id tid
FROM student s, teacher t
WHERE s.tid = t.id

子查询

  • 按照查询嵌套处理

配置实现类

<mapper namespace="com.hui.dao.TeachMapper">
    <!-- 按结果嵌套查询 -->
    <select id="getTeacher" resultMap="TeacherStudent">
        SELECT * FROM mybatis.teacher where id = #{tid}
    </select>

    <resultMap id="TeacherStudent" type="Teach">
        <result property="id" column="id" />
        <result property="name" column="name" />

        <collection property="students" ofType="Student" javaType="ArrayList"
                    column="id" select="getTeacherByStudentId" />
    </resultMap>

    <select id="getTeacherByStudentId" resultType="Student">
        SELECT * FROM mybatis.student where tid = #{id}
    </select>
</mapper>

Teacher(id=1,

name=老师,

students=[ Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)] )

关联查询

关联查询(结果嵌套查询

  • 接口类
// 获取指定老师信息和旗下所有学生
Teacher getTeacher(@Param("tid") int id);

配置实现类

  • 复杂属性标签处理:
    • association:对象, collection:集合
  • associationcollection 下指定返回类型
    • 指定属性的类型:javaType
    • 指定的目标为集合,表示泛型。使用:ofType
<mapper namespace="com.hui.dao.TeachMapper">
    <!-- 按结果嵌套查询 -->
    <select id="getTeacher" resultMap="TeacherStudent">
        SELECT s.id sid, s.`name` sname, t.`name` tname, t.id tid
        FROM mybatis.student s, mybatis.teacher t
        WHERE s.tid = t.id and t.id = #{tid}
    </select>

    <resultMap id="TeacherStudent" type="Teach">
        <result property="id" column="tid" />
        <result property="name" column="tname" />
        <!-- 集合使用 collection
              javaType="":指定属性的类型
              集合中的泛型使用 ofType 获取
        -->
        <collection property="students" ofType="Student" >
            <result property="id" column="sid" />
            <result property="name" column="sname" />
            <result property="tid" column="tid" />
        </collection>
    </resultMap>
</mapper>

返回结果:

Teacher(id=1,

name=老师,

students=[ Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1) ])

动态SQL

  • 根据不同的条件生成不同的SQL语句
  • 可以在SQL层面执行逻辑代码

12.1、搭建测试环境

  • sql 语句
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8
  • 实体类:com.hui.pojo.Blog.class
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
}
  • 新工具类:com.hui.Utlis.Utlis.class
// 用来生成随机id
public class IDutils {
    public static String getId() {
        return UUID.randomUUID().toString().replaceAll("-","");
    }
}
  • 接口类:com.hui.dao.BlogMapper.class
// 初始化默认博客
int addBlog(Blog blog);
  • 配置实现:com.hui.dao.BlogMapper.xml
<mapper namespace="com.hui.dao.BlogMapper">
    <insert id="addBlog" parameterType="blog" >
        insert into mybatis.blog (id, title, author, create_time, views)
        value (#{id} ,#{title},#{author},#{createTime},#{views})
    </insert>
</mapper>
  • 测试类插入数据

//-------------------------------
@Test
public void addInitBlog() {
    SqlSession sqlSession = MybatisUtils.sqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

    Blog blog = new Blog();
    blog.setId(IDutils.getId());
    blog.setTitle("第一篇文章");
    blog.setAuthor("hui3c");
    blog.setCreateTime(new Date());
    blog.setViews(9999);

    mapper.addBlog(blog);
    sqlSession.commit();

    blog.setId(IDutils.getId());
    blog.setTitle("第三篇文章");
    mapper.addBlog(blog);
    sqlSession.commit();

    blog.setId(IDutils.getId());
    blog.setTitle("第四篇文章");
    mapper.addBlog(blog);
    sqlSession.commit();

    blog.setId(IDutils.getId());
    blog.setTitle("第五篇文章");
    mapper.addBlog(blog);
    sqlSession.commit();

    sqlSession.close();
}

12.2、IF

需求:如果传入title就根据title查询,如果传入author就根据author 查询,如果什么都不传,就全查寻找

提示:这里只是为了介绍if知识点才这么去用,后面不需要写1=1

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog where 1 = 1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>
  • 满足条件后会在后面拼接上

  • 测试类:增加不同的条件即可获得不同的结果

    • if 标签会匹配到第一个就不继续匹配了,如titleauthor都有的情况下指挥先匹配title(例子)
List<Blog> queryBlogIF(Map map);
// ------------------------
public void test() {
        SqlSession sqlSession = MybatisUtils.sqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        HashMap map = new HashMap();
        map.put("title","第一篇文章");
        map.put("author", "hui3c");

        List<Blog> blogs = mapper.queryBlogIF(map);

        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }

where 标签

  • 不可能每次都在sql语句中写1 = 1 ,但如果不这么写又会导致当第一个条件不满足时,sql出现下述情况
SELECT * FROM blog WHERE AND title = "第一篇文章"
  • 显然WHERE后面接AND就会出现问题
  • 引出<where> 标签
    • <where> 标签会自动修复if中多余的AND或者OR
    • 什么都不传则会把标签去掉
  • 使用后效果
<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog where
    <where>
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

12.2、choose

有时我们不想应用到所有的条件语句,而指向从中选择以像,类似switch语句

需求:当有title时根据title查询(之后的不执行),当有author时根据author查询(之后的不执行),当什么都没时根据views查询

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from mybatis.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>

测试类传值:

HashMap map = new HashMap();
map.put("author", "hui3c");
map.put("title","第一篇文章");
map.put("views", 9999);

// 传值到该配置类。这里省略了接口
List<Blog> blogs = mapper.queryBlogChoose(map);

测试结果:

  • 三个都传时:根据title查询(之后的就不执行了)
  • 传入authorviews时:根据author查询(之后的就不执行了)
  • 传入其他除设定外任何参数和views时,根据views查询

12.3、set

<set> 会帮你去掉无关逗号和set关键字

注意:最好是都写上逗号,因为他只能减多余的逗号,而不能增加逗号

分析sql语句

UPDATE mybatis.blog SET title=#{title},author=#{author},views=#{views} WHERE id=#{id}
  • 因为sql语句拼接:当title不传时,就没有 SET 关键词
    • UPDATE mybatis.blog author=#{author}
  • 因为拼接:当views不传时,author后有个逗号
    • author=#{author},

为了测试方便,这里将id改成1234了

<insert id="updateBlog" parameterType="map">
    UPDATE mybatis.blog
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author}
        </if>
    </set>
    where id = #{id}
</insert>

测试类:有啥就修改啥

HashMap map = new HashMap();
map.put("author", "hui3c 改1");
map.put("title","第一篇文章 改1");

map.put("id", 1);

12.4、trim和foreach

<trim>标签可以定义为更高级的whereset功能,它允许用户自定义修补什么内容

  • prefix:前缀
  • prefixOverrides:后缀
  • suffixOverrides:分割符

如:<where><trim>标签表示可以如下:

<trim prefix="WHERE" prefixOverrides="AND">
  ...
</trim>

<set><trim>表示可以如下

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

foreach

  • 可以自定义遍历指定列表内的元素作为查询sql语句的条件

现有一sql语句,其目的是:

  • 当不传入任何参数时,返回全部结果;当传入多个id时,查询对应id的结果
SELECT * FROM mybatis.blog WHERE 1=1 AND (id=1 OR id=2 OR id=3)

使用<foreach>标签编写上述sql

  • <where> 会把1=1去了
  • collection:接收后集合的名字,item:集合内每一项的名字
  • open:以什么开始,close:以什么结束,separator:以什么分割
    • 这里还有一个参数index没使用到,用来标识下标
<select id="queryBlogForeach" parameterType="map" resultType="blog">
    SELECT * FROM mybatis.blog
    <where>
        <foreach collection="ids" item="Fid" open="and (" close=")" separator="or">
            id = #{Fid}
        </foreach>
    </where>
</select>

测试类

 List<Blog> queryBlogForeach(Map map);
// ---------------------------------
public void queryBlogForeachTest() {
        SqlSession sqlSession = MybatisUtils.sqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        HashMap map = new HashMap();
        ArrayList<Integer> ids = new ArrayList<>();
        // 要查询的id
        ids.add(1);
        ids.add(3);

        // map中key为ids,值为一个数组
        map.put("ids", ids);
        List<Blog> blogs = mapper.queryBlogForeach(map);

        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        sqlSession.commit();
        sqlSession.close();
    }

代码及流程

  1. 新建一个Map和一个列表,把需要查询的id插入到数组中,将此数组命名为ids
  2. Map指定keyidsvalue为刚刚创建的数组ids
  3. 循环传入Mapkey为idsmap
  4. 将每一项作为id来查询

最后的<foreach>,将and省略后,可以写成

<foreach collection="ids" item="Fid" open="(" close=")" separator="or">

sql语句执行

== Preparing: SELECT * FROM mybatis.blog WHERE ( id = ? or id = ? )
== Parameters: 1(Integer), 3(Integer)
== Columns: id, title, author, create_time, views
== Row: 1, 第一篇文章 改1, hui3c 改1, 2021-03-27 20:21:21.0, 9999
== Row: 3, 第四篇文章, hui3c, 2021-03-27 20:21:21.0, 9999
== Total: 2

12.5、SQL片段

用于实现代码的复用

  • 在SQL标签抽取公共部分
  • 在需要使用的地方使用<include>标签引用即可
<sql id="if-title">
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>

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

注意事项

  • 基于单表来定义SQL片段!
  • 不要使用where 标签
  • 包含的时候,尽量只用if

缓存

每次查询都需要连接数据库,会消耗大量的资源

一次查询的结果,给他暂存在内存中。这叫,缓存

再此查询时就不需要走数据库了,直接读内存

  • 为了解决高并发问题
  • 经常查询且不经常改变的数据

Mybatis 默认定义了而两种缓存:一级缓存二级缓存

  • 默认情况下,只一一级缓存。(SqlSession级别,作用域为创建sqlsession.close

二级缓存

  • 在配置文件中加入标签<cache />
  • 在该mapper的作用域下就开启二级缓存了

完结撒花,将来如果有需要再填坑吧~~


文章作者: Hui3c
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-ND 4.0 许可协议。转载请注明来源 Hui3c !
  目录