mybatis笔记

本文详细介绍了MyBatis框架的概述、环境搭建步骤、项目创建过程,以及核心配置文件的配置,包括类型别名、SQL映射文件、日志配置和环境设置。此外,还探讨了参数传递、结果处理、多表关联查询、动态SQL、缓存机制和类型处理器。通过对MyBatis的深入理解,可以更高效地进行数据库操作。
摘要由CSDN通过智能技术生成

MyBatis笔记

一.概述

MyBatis原是Apache的一个开源项目iBatis, 2010年由Apache Software Foundation 迁移到了 Google Code, iBatis3.x正式更名为MyBatis。是 一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps和Data Access Objects (DAO)。

MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录,是 一种 ORM(ORM Object Relational Mapping 对象关系映射)实现. Mybatis 将基本的 JDBC 常用接口封装,对外提供操作即可.

  • 是一个ORM框架.
    ORM(Object Relational Mapping)对象关系映射
  • 封装了一些关于数据库操作的接口,代替了JDBC
  • 对jdbc进行了封装,简化了其操作

Mybatis 中文官网: mybatis – MyBatis 3 | 入门

既然mybatis封装了JDBC,那么来看一下传统JDBC和MyBatis的区别

我们使用mybatis做一个小demo来看一下和传统JDBC的区别

使用maven构建一个Mybatis,在这里我使用的工具是IDEA

传统JDBC:

  1. 加载数据库驱动
  2. 创建并获取数据库链接
  3. 创建 statement 对象
  4. 拼写 sql 语句
  5. 设置 sql 语句中的占位符的值 (使用预编译可以防止sql注入)
  6. 执行 sql 语句并获取结果
  7. 对 sql 执行结果进行解析处理
  8. 释放资源

JDBC的编程问题

  • 数据库连接的创建、释放频繁造成系统资源浪费从而影响系统性能,如果使 用数据库连接池可解决该问题。
  • SQL 语句编写在 Java 代码中,这种硬编码造成代码不易维护,当 SQL 变动 时需要修改 java 源代码。
  • 使用 preparedStatement 向占位符传参数存在硬编码,因为 SQL 语句的 where 条件中占位符的个数可能会变化,修改 SQL 还要修改 Java 源代码, 系统不易维护.
  • 对结果集解析存在硬编码,SQL 语句变化导致解析代码变化,系统不易维护。

二. MyBatis 环境搭建

  1. 先创建一个maven的项目(搭建maven:https://blog.csdn.net/lanleihhh/article/details/120978091)

  2. 导入mybatis/mysql的坐标(maven仓库下载:https://mvnrepository.com/)

    在这里插入图片描述

在这里插入图片描述

复制后粘贴到中

	<!-- 导入 MyBatis jar 包数据库驱动包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <!-- 导入mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>

点击’M’形状的按钮安装jar包

在这里插入图片描述

我们可以使用日志组件(log4j)来记录程序的信息,引入log4j的坐标

		<!-- 导入log4j jar包 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

注意:引入log4j的坐标后,还需在resources文件中添加配置文件:log4j.properties

log4j.rootLogger = debug,stdout,D
#System out Console
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%p] %d{yyyy-MM-dd HH:mm:ss,SSS} %m%n

#System out File
log4j.appender.D = org.apache.log4j.FileAppender
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
	# 这里设置打印日志的文件路径
log4j.appender.D.File = E://logs/mybatisLog/LogMybatisDemo.log
	
log4j.appender.D.Append = true
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ] -[%l] %m%n

创建 MyBatis 全局配置文件 MyBatis 的配置文件包含了对 MyBatis 行为的设置信息。

配置文档的顶层 结构如下(标签需要按照特定顺序排放):

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

如下:

<?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"></transactionManager>
            <!--数据源-->
            <dataSource type="POOLED">
                <!--属性-->
                <property name="driver" value="" />
                <property name="url" value="" />
                <property name="username" value="" />
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>
    
    <!--映射器-->
    <mappers>
        <mapper resource="映射文件全类名.xml"/>
    </mappers>
</configuration>

三.创建Mybatis项目步骤

步骤:

1.根据需求设计一个类与数据库的表

Java类:

package com.ffyc.mybatispro.model;

public class Admin {
    private int id;
    private String account;
    private String password;
    private String sex;
    
    public int getId() {
        return id;
    }

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

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Admin{" +
                "id=" + id +
                ", account='" + account + '\'' +
                ", password='" + password + '\'' +
                ", sex='" + sex + '\'' +
                ", userName='" + userName + '\'' +
                '}';
    }
}

数据库表:

CREATE DATABASE mybatis_db CHARSET utf8

CREATE TABLE admin(
  id INT PRIMARY KEY AUTO_INCREMENT,
  sex CHAR(1),
  account VARCHAR(20),
  PASSWORD VARCHAR(20)
)

在这里插入图片描述

2.接口(Mapper),实现对类的一些操作
package com.ffyc.mybatispro.mapper;
import com.ffyc.mybatispro.model.Admin;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface AdminMapper {
    //插入
    void saveAdmin(Admin admin);

    //更新
    void updateAdmin(Admin admin);

    //删除
    void deleteAdmin(int AdminId);

    //修改
    Admin findAdminById(int AdminId);

    //传多个参数,使用注解标签@Param("acc")  acc:传向xml文件中 #{acc}
    Admin findAdmin(@Param("acc")String account,@Param("sex")String sex);

    //传入键值对参数
    Admin findAdmin(Map<String,Object> map);

    //查询所有管理员对象
    List<Admin> findAllAdmin();

    //查询管理员总人数
    int adminCount();
}
3.全局配置文件(mybatis.xml)配置信息

几个.xml配置文件的区别

在这里插入图片描述

< typeAliases>为类定义别名
  • type:全类名
  • alias:别名
	<!--为类定义别名-->
    <typeAliases>
        <typeAlias type="com.ffyc.mybatispro.model.Admin" alias="Admin"></typeAlias>
    </typeAliases>
配置< mapper>(添加SQL映射文件)
<!--添加SQL映射文件-->
<mapper resource="mapper/AdminMapper.xml"/>
设置
Mybatis日志

Mybatis 内置的日志工厂提供日志功能,具体的日志实现有以下几种方式:

  • SLF4J
  • LOG4J
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING
  • NO_LOGGING

具体选择哪个日志实现由 MyBatis 的内置日志工厂确定。它会使用最先找到的配置日志

<settings>
    <!-- 配置日志实现, 使用log4j,添加log4j -->
    <setting name="logImpl" value="LOG4J"/>
</settings>
配置环境
   <environments default="development">
        <!--配置与数据库连接的相关信息-->
        <environment id="development">
            <!--配置事务管理类型:JDBC-->
            <transactionManager type="JDBC"></transactionManager>
            <!--数据源:配置连接池与数据库连接的参数
                    UNPOOLED:每次创建新的连接,用完销毁
                    POOLED:使用已有的连接,用完返回
            -->
            <dataSource type="UNPOOLED">
         		<!-- ${} 拼接符,会传入参数字符串
				我们创建一个properties文件用来存储连接数据库的信息 -->
                <property name="driver" value="${driver}" />
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
创建config. properties(存储与数据库连接的信息)
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mybatis_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=lanlei6843
引入config. properties
<!--引入存放值的属性文件 -->
<properties resource="config.properties"></properties>
4.接口对应的配置文件(AdminMapper.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace:命名空间,写接口的全类名-->
<mapper namespace="com.ffyc.mybatispro.mapper.AdminMapper">
    	<!--
         id:与接口中方法名相同
         parameterType:参数类型,简单类型(int等)可以不写此属性
         resultType:返回值类型
         useGeneratedKeys:可以返回刚插入数据的主键
         keyColumn:告知主键列
         keyProperty:告知主键列对应的属性
         -->
	<insert id="saveAdmin" parameterType="Admin" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into admin(account,password,sex,user_name)values(#{account},#{password},#{sex},#{userName})
    </insert>
</mapper>

测试方法:saveAdmin()

package com.ffyc.mybatispro.test;

import com.ffyc.mybatispro.mapper.AdminMapper;
import com.ffyc.mybatispro.model.Admin;
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;
import java.io.Reader;


public class Test1 {
    public static void main(String[] args) throws IOException {
        Admin admin = new Admin();
              admin.setAccount("admin");
              admin.setPassword("123");
              admin.setSex("男");
        
        /*
        1.读取 mybatis 核心配置文件
        sql写在xml文件中,我们不能直接访问,mybatis让我们在业务代码中可以定义接口,在接口中定义操作的方法
        方法定义要求:
        方法名=xml中对应的标签的id
        方法中的参数类型和返回值类型与xml中一致
        * */
        InputStream reader = Resources.getResourceAsStream("mybatis.xml");

        /*
        2.创建sqlSessionFactory对象
        创建sqlSession的工厂,负责创建sqlSession对象    sqlSessionFactory中包含核心配置信息
        由于sqlSessionFactory对象创建开销较大,所以一旦创建后就不会销毁,一个应用程序只有一个sqlSessionFactory
        */
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

        /*
        3.创建sqlSession对象,表示一次与数据库连接会话,类似于JDBC的Connection
        每次与数据库交互,都会创建一个sqlSession对象,使用完后会立即销毁释放
        */
        SqlSession sqlSession= sqlSessionFactory.openSession();
        //获得映射接口的代理对象
        //MapperProxy动态实现了自定义接口
        //adminMapper对象实质是MapperProxy对象 org.apache.ibatis.binding.MapperProxy@79ad8b2f
        //使用代理对象
        AdminMapper adminMapper =  sqlSession.getMapper(AdminMapper.class);
                    adminMapper.saveAdmin(admin);
                    sqlSession.commit();//提交事务
                    sqlSession.close();//关闭与数据库会话连接

    }
}

运行一下:

在这里插入图片描述

查看数据库:

在这里插入图片描述

我们使用另一中传值方式

<insert id="saveAdmin" parameterType="Admin" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into admin(account,password,sex)values('${account}','${password}','${sex}')
    </insert>

运行:

在这里插入图片描述

	传列:  ${属性名}
    传值: 
	'${属性名}':${} 拼接符,会传入参数字符串,取值以后再去编译 SQL 语句,$方式无法防止sql注入
	#{属性名} :#{} 占位符,是经过预编译的,编译好 SQL 语句再取值,#方式能够防止 sql 注入
	values('','',''):字符串拼接方式
    values(?,?,?):预编译,是安全的,可以防止sql注入

注意:MyBatis 排序时使用 order by 动态参数时需要注意,用$而不是#

驼峰映射

在mybatis中默认java属性名和数据库字段名一一对应

如:

java: account

mysql:account

但两者定义语法不同

  • java属性采用小驼峰(userName)
  • 数据字段采用下划线(user_name)

很多时候没法将Java属性与数据库字段定义为相同的,因为要遵循规范

解决方法:

  1. 在sql语句中使用As设置别名,如(user_name As userName),设置与Java属性相同

  2. mybatis的settings配置中有一个namemapUnderscoreToCamelCase的参数,默认是false,设置为true,即可开启驼峰映射

    可以从Java属性userName映射到数据库字段user_name,方便了很多

<!--开启驼峰映射  设置启用数据库字段下划线映射到java对象的驼峰式命名属性,默认为false-->
<setting name="mapUnderscoreToCamelCase" value="true"/>

我们测试一下

在数据库中修改表列命名为经典下划线

-- 修改列名
ALTER TABLE admin CHANGE PASSWORD pass_word VARCHAR(20) 

在这里插入图片描述

在java中修改属性

//private String password;原属性
private String passWord;

sql映射中修改sql语句

insert into admin(account,pass_word,sex)values(#{account},#{passWord},#{sex})

测试运行:

在这里插入图片描述

成功运行了,这就是驼峰映射

我们将属性与字段再修改为原来的password

封装步骤

将步骤繁多的流程封装起来,我们使用的时候直接访问该封装类的方法

package com.ffyc.mybatispro.util;

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 {

    static SqlSessionFactory sqlSessionFactory = null;

    static {
        /*1.读取 mybatis 核心配置文件*/
        InputStream reader = null;
        try {
            reader = Resources.getResourceAsStream("mybatis.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        /*2.创建sqlSessionFactory对象*/
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }

    public static SqlSession getSqlSession(){//返回sqlSession对象
        return sqlSessionFactory.openSession();
    }
}

单元测试

概念

  • java单元测试是最小的功能单元测试代码

  • 单元测试就是针对某个Java方法的测试

  • java程序的最小功能单元是方法

使用单元测试的好处

  • 不用再在main()方法中一个一个调用,测试完毕后再删除
  • 确保单个方法正常运行
  • 每个单元测试用例相对独立,不需要添加额外语句
  • 只需查看测试结果,就可以了解整个项目的的方法接口是否通畅

我们借助juit工具进行测试

先在pom.xml中依赖单元测试的jar包,将其jar包下载到本地仓库中,然后依赖它

		<!-- 依赖单元测试 jar包 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>provided</scope>
        </dependency>

在想要测试的方法前添加注解标签@Test,如下:

	@Test
    public void update(){
        Admin admin = new Admin();
        admin.setAccount("admin");
        admin.setPassword("123456");
        admin.setSex("男");
        //创建SqlSession对象
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        //获取代理对象
        AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
        mapper.updateAdmin(admin);
        sqlSession.commit();//提交事务
        sqlSession.close();//关闭连接
    }

在这里插入图片描述

类型别名

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

<!--为类定义别名-->
<typeAliases>
    <typeAlias type="com.ffyc.mybatispro.model.Admin" alias="Admin"></typeAlias>

    <typeAlias type="com.ffyc.mybatispro.model.Dept" alias="Dept"></typeAlias>
    <typeAlias type="com.ffyc.mybatispro.model.Employee" alias="Employee">	</typeAlias>
</typeAliases>

当这样配置时,Admin可以在任何使用 com.ffyc.mybatispro.model.Admin 的地方。

可以在mybatis的jar包中找org.apache.ibatis.type.TypeAliasRegistry

里面定义了各种基本类型和引用类型的别名;如图

在这里插入图片描述

下面是mybatis为Java类库中的一些类和简单类型定义的别名

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

参数传递

1.简单的参数形式不需要使用 parameterType 参数定义

如:

	//删除
    void deleteAdmin(int AdminId);

    //修改
    Admin findAdminById(int AdminId);	
	<!--删除-->
    <delete id="deleteAdmin">
        delete from admin where id=#{id}
    </delete>

    <!--查找-->
	<select id="findAdminById" resultType="Admin">
        SELECT * FROM admin WHERE id=#{id}
    </select>
2. 多个参数使用@Param(“id”)绑定

Mapper接口方法

	//传多个参数,使用注解标签@Param("acc")  acc:传向xml文件中 #{acc}
    Admin findAdmin(@Param("acc")String account,@Param("sex")String sex);

xml中的实现

	<select id="findAdmin"  resultType="Admin">
        SELECT * FROM admin WHERE account=#{acc} and sex=#{sex}
    </select>

java测试代码

 	@Test
    public void find3(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
        Admin admin = mapper.findAdmin("admin","男");
        System.out.println(admin);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

3.传入复杂的参数,使用Map对象传递

Mapper接口方法

	//传入键值对参数
    Admin findAdmin(Map<String,Object> map);

xml中的实现

对于简单类型可不写parameterType属性,使用Java类库中的类作为参数时,使用mybatis为其定义的别名

这里可以通过mybatis3官方手册查看,XML配置–>类型别名https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases

    <!-- java中已有的类作为类型使用时,可以直接使用mybatis定义的别名 -->
	<select id="findAdmin"  resultType="Admin" parameterType="hashmap">
        SELECT * FROM admin WHERE account=#{acc} and sex=#{sex}
    </select>

java测试代码

 	@Test
    public void find4(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
        Map<String,Object> map = new HashMap<>();
        map.put("acc", "admin");
        map.put("sex", "男");
        Admin admin = mapper.findAdmin(map);
        System.out.println(admin);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

结果处理

简单类型输出映射

返回基本类型

 	//查询管理员总人数
    int adminCount();
	<!--resultType:返回值类型 id:接口中方法名-->
	<select id="adminCount"  resultType="int">
        SELECT count(*) FROM admin
    </select>
	@Test
    public void find6(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
        int count = mapper.adminCount();
        System.out.println("管理员总人数:"+count);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

POJO对象输出映射
  • 如果表中的类名与类中的属性名完全相同,mybatis会自动将查询结果封装 到POJO对象中
  • 如果java中使用标准驼峰命名,数据库中使用下划线连接命名,可以开启全局设置实现自动转换

resultMap:

  • resutlMap 的 id 属性是 resutlMap 的唯一标识
  • resutlMap 的 id 属性是映射的 POJO 类
  • id 标签映射主键,result 标签映射非主键
  • property 设置 POJO 的属性名称,column 映射查询结果的列名称
<setting name="mapUnderscoreToCamelCase" value="true"/>

将admin表中列名password修改为pass_word

	<select id="findAdminById" resultMap="adminMap">
        SELECT id,account,pass_word,sex FROM admin WHERE id=#{id}
    </select>

定义resultMap

	<!--结果集(用于多表关联,此处测试列名与属性名不同时的处理)-->
    <resultMap id="adminMap" type="Admin">
        <id column="id" property="id"></id>
        <result column="account" property="account"></result>
        <result column="pass_word" property="password"></result>
    </resultMap>

在这里插入图片描述

多表关联处理查询结果集
  • association – 复杂类型联合

    许多查询结果合成这个类型 一对一结果映射

    association 能引用自身, 或者从其它地方引用

    处理一个类型的关系(类)

  • collection – 复杂类型集合 嵌套结果映射

    collection 能引用自身, 或者从其它地方引用 多对一与一对多

    处理一对多关联(集合)

    • 部门与员工一对多

      在这里插入图片描述

    • 多个员工对应一个部门(多对一)

      在这里插入图片描述

package com.ffyc.mybatisdemo.mapper;

import com.ffyc.mybatisdemo.model.Dept;

import java.util.List;

public interface DeptMapper {
	//根据id查部门
    Dept findDeptById(int id);
    //查所有部门
    List<Dept> findDeptList();
}

sql映射文件

<?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">
<mapper namespace="com.ffyc.mybatisdemo.mapper.DeptMapper">


    <select id="findDeptById" resultMap="deptMap">
        SELECT
        d.id,
        d.name dname,
        e.name ename,
        a.account
        FROM dept d LEFT JOIN employee e ON d.id=e.dept_id
                     LEFT JOIN admin a ON d.admin_id=a.id
                     WHERE d.id=1
    </select>

    <select id="findDeptList" resultMap="deptMap">
        SELECT
        d.id,
        d.name dname,
        e.name ename,
        a.account
        FROM dept d LEFT JOIN employee e ON d.id=e.dept_id
		     LEFT JOIN admin a ON d.admin_id=a.id
    </select>

    <!--使用resultMap组装查询结果-->
    <resultMap id="deptMap" type="Dept">
        <id column="id" property="id"></id>
        <result column="dname" property="name"></result>
        <association property="admin" javaType="Admin">
            <result property="account" column="account"></result>
        </association>
        <collection property="employeeList" javaType="list" ofType="Employee">
            <result column="ename" property="name"></result>
        </collection>
    </resultMap>
    
</mapper>

测试根据id查部门

	@Test
    public void  findDept(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);

        Dept dept = mapper.findDeptById(1);
        List<Employee> deptList = dept.getEmployeeList();
        System.out.println("部门"+dept.getName());
        for(Employee employee : deptList){
            System.out.println("所属员工:"+employee.getName());
        }
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

测试查所有部门

	@Test
    public void  findDeptList(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
        List<Dept> depts = mapper.findDeptList();
        for(Dept dept : depts){
            System.out.println("部门名称:"+dept.getName());
            for(Employee emp : dept.getEmployeeList()){
                System.out.println("部门职工:"+emp.getName());
            }
            System.out.println("操作人:"+dept.getAdmin().getAccount());
        }
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

延迟加载&嵌套查询

​ 需要查询关联信息时,使用 Mybatis 延迟加载特性可以有效的减少数据库压力, 首次查询只查询主表信息,关联表的信息在用户获取时再加载。

​ Mybatis 一对一关联的 association和一对多的 collection 可以实现延迟加载。加载时要使用 resultMap,不能使用 resultType

启用延迟加载(懒加载)

<settings>
    <!--延迟加载的全局开关。 默认值false
当开启时,所有关联对象都会延迟加载通过设置fetchType属性来覆盖该项的开关状态。-->
	<setting name="lazyLoadingEnabled" value="true"/>
</settings>

指定触发延迟加载的方法

<!--延迟加载 lazyLoadTriggerMethods:指定对象的哪些方法触发一次延迟加载
	默认值:equals,clone,hashCode,toString-->
<setting name="lazyLoadTriggerMethods" value=""/>

来一个实例展示一下延迟加载的效果,将原本多表关联的sql语句拆分为嵌套查询

原多表关联查询

	<select id="findEmployeeById" resultMap="employeeMap">
        SELECT
        e.name ename,
        e.age,
        e.sex,
        d.name dname,
        d.id did,
        a.account
        FROM  employee e
        LEFT JOIN dept d ON e.dept_id=d.id
        LEFT JOIN admin a ON e.admin_id=a.id
        WHERE e.id=#{id}
    </select>

嵌套查询

	<!--懒加载,不能用resultType,只能用resultMap-->
    <select id="findEmployeeByIdTestLazy" resultMap="employeeMap">
        select name,age,sex,dept_id,admin_id from employee where id=#{id}
    </select>

    <select id="findAdminTestLazy" resultType="Admin">
        select account from admin where id=#{admin_id}
    </select>

    <select id="findDeptTestLazy" resultType="Dept">
        select name from dept where id=#{dept_id}
    </select>

结果集

<resultMap id="employeeMap" type="Employee">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="age" property="age"></result>
        <result column="sex" property="sex"></result>
        <!--
        fetchType:lazy 开启延迟加载(需要时采取触发查询)前提:将多表查询(一条sql)写成嵌套查询(多条sql)
        select:与select标签的id值相同
        -->
        <association property="admin" column="admin_id" javaType="Admin"
        fetchType="lazy" select="findAdminTestLazy">
            <result property="account" column="account"></result>
        </association>

        <association property="dept" column="dept_id" javaType="Dept"
                     fetchType="lazy" select="findDeptTestLazy">
            <result column="name" property="name"></result>
        </association>
    </resultMap>

现在测试一下延迟加载

	@Test//查询单个员工(测试延迟加载)
    public void find3(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        //在调用方法出打断点,来看一看延迟加载;正常程序应该是逐条执行,一次性取出employee对象的属性,我们来试一下
        Employee employee = mapper.findEmployeeByIdTestLazy(1);//打断点
        System.out.println(employee.getName());
        System.out.println(employee.getDept().getName());
        System.out.println(employee.getAdmin().getAccount());
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

注解方式

常用注解标签

  • @Insert : 插入 sql , 和 xml insert sql 语法完全一样
  • @Select : 查询 sql, 和 xml select sql 语法完全一样
  • @Update : 更新 sql, 和 xml update sql 语法完全一样
  • @Delete : 删除 sql, 和 xml delete sql 语法完全一样
  • @Param : 传入参数
  • @Results : 设置结果集合
  • @Result : 结果
	//简单sql语句可以直接写在接口方法的注解标签中,不用在xml文件中写
	@Delete("DELETE FROM employee WHERE id=#{id}")
	//@Param绑定参数,将其传给sql语句中的占位符处
    void del(@Param("id") int id);
	//删除
    @Test
    public void del1(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        mapper.del(4);//删除id为4的记录
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

执行方法

在这里插入图片描述

查询所有信息

@Select("select * from t_emp")
@Results(id = "empMap",value = {
@Result(column = "emp_id",property = "empId",id = true), 
@Result(column = "emp_name",property = "empName"), 
@Result(column = "emp_tel",property = "empTel"), 
@Result(column = "emp_education",property = "empEducation"), 
@Result(column = "emp_birthday",property = "empBirthday")
})
List<Employee> getAll();//接口方法

这几条注解标签相当于sql映射(.xml)文件中的resultMap,写起来相对麻烦一些

插入信息

@Insert("insert into t_emp (emp_id, emp_name, emp_tel, " +
" emp_education, emp_birthday, fk_dept_id" +
" )" values (#{empId}, #{empName}, #{empTel}, " +
" #{empEducation}, #{empBirthday}, #{fkDeptId}" +
" )")
int insert(Employee record);

在注解标签中写sql语句和传统JDBC一样,需要拼接许多字符串,而sql映射文件中不存在拼接字符串的情况

总结:

  • 如单个查询/删除等简单sql语句,可以直接在接口的方法上方使用注解标签写sql

  • 若sql语句较长,查询结果较多,写在映射文件中更简单方便一些

动态sql

​ 我们以前使用传统的JDBC写复杂一点的sql语句时,往往需要拼接字符串,容易因为标点符号错误或缺失等导致整条sql是不能运行的.Mybatis提供了动态sql来解决以前令人头疼的事.

​ 动态sql可以根据用户提供的参数,动态地决定查询语句的条件或sql语句的内容

Mybatis中用于实现动态sql的元素:

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

if元素

if标签可以对传入的条件进行判断

<select id="findEmployee" resultMap="employeeMap">
        SELECT
            e.id,
            e.name ename,
            e.age,
            e.sex,
            d.name dname,
            a.account
            FROM  employee e
        LEFT JOIN dept d ON e.dept_id=d.id
                <!--                where 1=1 不管name和age是否为空,总会正常执行-->
        LEFT JOIN admin a ON e.admin_id=a.id where 1=1
            <if test="name!=null &amp; name !=''">
                and e.name=#{name}
            </if>
            <if test="age!=null &amp; age !='' ">
                and e.age=#{age}
            </if>
    </select>

where

当查询条件的个数不确定的情况下,使用where元素.

可以根据里面if是否有成立的,动态添加where关键字,会自动删除and or 等开头的关键字

<select id="findEmployee" resultMap="employeeMap">
        SELECT
            e.id,
            e.name ename,
            e.age,
            e.sex,
            d.name dname,
            a.account
            FROM  employee e
        LEFT JOIN dept d ON e.dept_id=d.id
        LEFT JOIN admin a ON e.admin_id=a.id
        <where>
            <if test="name!=null &amp; name !=''">
                e.name  =#{name}
            </if>
            <if test="age!=null &amp; age !='' ">
                and e.age  =#{age}
            </if>
        </where>
    </select>

在这里插入图片描述

在这里插入图片描述

set

set元素可以去掉set语句中的最后一个逗号

	<update id="updateEmp" parameterType="Employee">
        update employee
        <set>
            <if test="name!=null">
                name =#{name},
            </if>
            <if test="sex!=null">
                sex =#{sex},
            </if>

            <if test="dept.id!=null">
                dept_id=#{dept.id},
            </if>
        </set>
        where id=1
    </update>
	@Test
    public void update(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Employee employee = new Employee();
                 employee.setName("王玄策");
                 employee.setSex("男");
        Dept dept = new Dept();
             dept.setId(2);
             employee.setDept(dept);
             mapper.updateEmp(employee);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

trim

使用trim元素也可以实现where 和 set 元素的功能

trim实现where

	<!--前缀为where-->
	<select id="findEmployee" resultMap="employeeMap">
        SELECT
        e.id,
        e.name ename,
        e.age,
        e.sex,
        d.name dname,
        a.account
        FROM  employee e
        LEFT JOIN dept d ON e.dept_id=d.id
        LEFT JOIN admin a ON e.admin_id=a.id
        <trim prefix="where" prefixOverrides="and|or">
            <if test="name!=null &amp; name !=''">
                and  e.name  =#{name}
            </if>
            <if test="age!=null &amp; age !='' ">
                and e.age  =#{age}
            </if>
        </trim>
    </select>

实现set

	<!--前缀为set-->
	<update id="updateEmp" parameterType="Employee">
        update employee
        <trim prefix="set" suffix="where" suffixOverrides=",">
            <if test="name!=null">
                name =#{name},
            </if>
            <if test="sex!=null">
                sex =#{sex},
            </if>

            <if test="dept.id!=null">
                dept_id=#{dept.id},
            </if>
        </trim>
        id=1
    </update>

choose(when,otherwise)

多路选择,和Java中的switch类似

when相当于switch中的case,当<when test="条件 ">条件成立,执行when中的语句

otherwise则类似default,当都不成立时,执行它

	<!--  &amp;是&的转义符  -->
	<select id="findEmployee" parameterType="Employee" resultMap="employeeMap">
        SELECT
        e.id,
        e.name ename,
        e.age,
        e.sex,
        d.name dname,
        a.account
        FROM  employee e
        LEFT JOIN dept d ON e.dept_id=d.id
        LEFT JOIN admin a ON e.admin_id=a.id
        <trim prefix="where" prefixOverrides="and">
            <choose>
                <when test="name!=null &amp; name!='' ">
                  and  e.name  =#{name}
                </when>
                
                <when test="age!=null &amp; age!='' ">
                  and  e.name='李四'
                </when>
                
                <otherwise>
                  and e.name='里利'
                </otherwise>
            </choose>
        </trim>
    </select>

当第一个when的条件成立时(name值不为空or空串)

在这里插入图片描述

当第二个when的条件成立时(age不为空时)

在这里插入图片描述

当条件不成立时(执行otherwise里的语句)

在这里插入图片描述

foreach

主要用在构建In中

SELECT * FROM employee WHERE age IN(20,22,24)

可以在 SQL 语句中进行迭代一个集合。

主要属性:

  • item: 表示集合中每一个元素进行迭代时的别名

  • index: 指定一个名字,用于 表示在迭代过程中,每次迭代到的位置

  • open: 表示该语句以什么开始

  • separator: 表示在每次进行迭代之间以什么符号作为分隔符

  • close: 表示以什么结束

  • collection: 该属性是必须指定的,但是在不同情况下,该属性的值是不一样的

    • 如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 list
    • 如果传入的是单参数且参数类型是一个 array 数组的时候, collection 的属性值为 array
  1. 使用集合作为参数,测试foreach的效果
	//根据年龄集合查找年龄范围内的员工
    List<Employee> findEmployeeByAge(List<Integer> list);
	<select id="findEmployeeByAge" resultType="Employee">
        select * from employee where age in
        <foreach item="age" collection="list" open="(" separator="," close=")">
            #{age}
        </foreach>
    </select>
	@Test//传入集合测试foreach
    public void find4(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        List<Integer> list = new ArrayList<>();
                      list.add(20);
                      list.add(21);
                      list.add(22);
              mapper.findEmployeeByAge(list);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

  1. 使用数组作为参数,测试foreach的效果
    //根据年龄数组查找年龄范围内的员工
    List<Employee> findEmployeeByAge1(Integer[] arr);
	<select id="findEmployeeByAge1" resultType="Employee">
        select * from employee where age in
        <foreach item="age" collection="array" open="(" separator="," close=")">
            #{age}
        </foreach>
    </select>
 	@Test//传入数组测试foreach
    public void find5(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Integer[] arr = new Integer[]{20,21,22};
        mapper.findEmployeeByAge1(arr);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

转义符

在 mybatis 中的 xml 文件中,存在一些特殊的符号,比如:<、>、"、’&、<> 等,正常书写 mybatis 会报错,需要对这些符号进行转义。具体转义如下所示: 特殊字符 转义字

  • < &lt;

  • > &gt;

  • " &quot;

  • ' &apos;

  • & &amp;

	<!--查询年龄小于指定参数的员工-->
	<select id="findEmployeeByAge2" resultType="Employee">
        select * from employee where age &lt; #{age}
    </select>
	@Test//找年龄<21的
    public void find6(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        mapper.findEmployeeByAge2(21);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

除了可以使用上述转义字符外,还可以使用<![CDATA[ ]]>包裹特殊字符 . <![CDATA[ ]]>是 XML 语法。在 CDATA 内部的所有内容都会被解析器忽略。

 	<select id="findEmployeeByAge2" resultType="Employee">
        select * from employee where age  <![CDATA[ < ]]> #{age}
    </select>

我们可以将需要转义才能被解析的字符放在<![CDATA[ 特殊符号 ]]>

mybatis缓存

为什么使用缓存?

​ 由于从硬盘读取数据相对来说比较,当大批量查询(淘宝双11点击量暴增)时,数据库的压力较大,可以将第一次查询到的数据放在内存中而不销毁它,下次若查询该数据时可以直接从内存中获取,减少对数据库的查询次数,提高了数据库的性能.

​ 缓存是使用 Map 集合缓存数据的 .

  • 一级缓存:sqlSession级别,在一次会话中保存数据,会话结束后缓存中的数据销毁
  • 二级缓存: SqlSessionFactory级别的

一级缓存

​ Mybatis 有一级缓存和二级缓存。一级缓存的作用域是同一个 SqlSession, 在同一个 sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库 中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个 sqlSession 结束后该 sqlSession 中的一级缓存也就不存在了。Mybatis 默认开启一级缓存 .

​ 我们使用上面的按年龄查询的方法测试mybatis的一级缓存

两个对象执行相同的查询,查询次数为2,因为一级缓存在与同一个sqlSession对象中

	@Test//找年龄<21的
    public void find6(){
    	//sqlSession对象1
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        mapper.findEmployeeByAge2(21);
        sqlSession.commit();
        sqlSession.close();
		
		//sqlSession对象2
        SqlSession sqlSession1 = MybatisUtil.getSqlSession();
        EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);
        mapper1.findEmployeeByAge2(21);
        sqlSession1.commit();
        sqlSession1.close();
    }

在这里插入图片描述

一个对象执行两次相同的查询,只查询了一次

	@Test//找年龄<21的
    public void find6(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        mapper.findEmployeeByAge2(21);
        mapper.findEmployeeByAge2(21);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

一个对象执行两次不同的查询

	@Test//找年龄<21的
    public void find6(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        mapper.findEmployeeByAge2(21);
        mapper.findEmployeeByAge2(22);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

生命周期
  1. MyBatis 在开启一个数据库会话时,会创建一个新的 SqlSession 对象, SqlSession 对象中会有一个新的 Executor 对象。Executor 对象中持有一个新的 PerpetualCache 对象,如果 SqlSession 调用了 close()方法,会释放掉一级 缓存 PerpetualCache 对象,一级缓存将不可用。
  2. 如果 SqlSession 调用了 clearCache(),会清空 PerpetualCache 对象 中的数据,但是该对象仍可使用。
  3. SqlSession 中执行了任何一个 update 操作(update()、delete()、 insert()) ,都会清空 PerpetualCache 对象的数据,但是该对象可以继续使用

清空一级缓存

  • close()关闭会话,清空一级缓存

  • clearCache()清空一级缓存

  • 增删改清空一级缓存

clearCache()清空一级缓存

	@Test
    public void find7(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

        mapper.findEmployeeByAge2(21);

        //clearCache清空一级缓存的数据
        sqlSession.clearCache();

        mapper.findEmployeeByAge2(21);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

增删改清空一级缓存

	@Test//增删改会清空当前SQLSession对象中的缓存(防止数据脏读)
    public void find8(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

        mapper.findEmployeeByAge2(21);

        Employee employee = new Employee();
        employee.setName("王玄策123");
        employee.setSex("男");
        Dept dept = new Dept();
        dept.setId(2);
        employee.setDept(dept);
        mapper.updateEmp(employee);

        mapper.findEmployeeByAge2(21);
        sqlSession.commit();
        sqlSession.close();
    }

在这里插入图片描述

二级缓存

​ 二级缓存是SqlSessionFactory级别的(SqlSessionFactory只有一个),根据Mapper的namespace 划分区域的.

​ 相同 namespacemapper 查询的数据缓存在同一个区域,如果使用 mapper 代理方法每个 mappernamespace 都不同,此时可以理解为二级缓存区域是根据 mapper 划分。

每次查询会先从缓存区域查找,如果找不到则从数据库查询,并将查询到数据写入缓存。Mybatis 内部存储缓存使用一个 HashMapkeyhashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象。 sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域,防止脏读。

配置二级缓存

  1. 启用二级缓存

    mybatis默认不开启二级缓存

    <setting name="cacheEnabled" value="true"/>
    
  2. POJO序列化(所有)

    所有POJO类(自定一类)实现序列化接口( Java.io. Serializable )

  3. 配置映射文件(<cache/>)

    在 Mapper 映射文件中添加<cache/>,表示此 mapper 开启二级缓存

    当 SqlSeesion 关闭时,会将数据存入到二级缓存

当一个SqlSession对象关闭后(一次会话结束),数据存入二级缓存

SqlSessionFactory中的其他SqlSession对象依旧可以使用二级缓存中的数据

@Test
    public void find9(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        mapper.findEmployeeByAge2(21);
        sqlSession.commit();
        sqlSession.close();
        //sqlSession关闭后,将数据存放到二级缓存中,同一个sqlSessionFactory的sqlSession对象共缓存

        SqlSession sqlSession1 = MybatisUtil.getSqlSession();
        EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);
        mapper1.findEmployeeByAge2(21);
        sqlSession1.commit();

        SqlSession sqlSession2 = MybatisUtil.getSqlSession();
        EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
        mapper2.findEmployeeByAge2(21);
        sqlSession2.commit();

    }

在这里插入图片描述

类型处理器(typeHandlers) 入参和查询结果 将java类型与数据库类型进行匹配转账

  • BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
  • ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERIC 或 BYTE
  • ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERIC 或 SMALLINT
  • IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERIC 或 INTEGER
  • LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERIC 或 BIGINT
  • FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERIC 或 FLOAT
  • DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERIC 或 DOUBLE
  • BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERIC 或 DECIMAL
  • StringTypeHandler java.lang.String 数据库兼容的 CHAR, VARCHAR
  • ClobReaderTypeHandler java.io.Reader -
  • ClobTypeHandler java.lang.String
    数据库兼容的 CLOB, LONGVARCHAR
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值