Epiphyllum

Mybatis

mybatis#

一,常用框架#

Struts1 Struts2 SpringMVC框架,类似于servlet,用来处理请求和给出响应

Springmvc spring的一个分支, mvc框架,类似于servlet,Struts2,处理请求和给出响应,可以替代 struts2

Hibernate 持久化框架, 类似于jdbc,连接数据库,操作数据库中表的数据,orm框架

Mybatis 持久化框架,类似于jdbc,hibernate,连接数据库,半自动化的ORM框架

mybatis的sql语句自己编写,sql灵活性较高,便于开发。

Mybatis-plus 是mybatis的工具库,是mybatis的升级,可以对单表进行自动生成sql实现各种功能

JPA可以自动生成sql语句。

MyBatis和Hibernate区别#

Hibernate<是一个标准orm框架>(对象关系映射)。#

  • 入门门槛较高的,不需要程序写Sql,Sql语句自动生成,对Sql语句进行优化、修改比较困难的;
  • 应用场景<适用与需求变化不多的中小型项目>,比如<后台管理系统>,erp、crm、oa...;

MyBatis<专注于sql本身>,需要程序员自己编写Sql语句,Sql修改、优化比较方便。#

  • MyBatis是一个不完全的ORM框架,虽然程序员自己写Sql,MyBatis也可以实现映射(输入映射、 输出映射);
  • 应用场景<适用与需求变化较多的项目>,比如<互联网项目>;

Spring 粘合剂,用来简化其他框架的使用及紧密的结合

Springboot 微服务框架

SSH (struts2 + spring + hibernate)

SSM (springmvc + spring + mybatis)

SM(springboot + mybatis + mybatis-plus)

二,mybatis概述#

1. JDBC编程存在的问题#

JDBC查询数据代码

image-20220825155334492

JDBC添加数据代码

image-20220825155350168

JDBC问题总结如下

  1. 数据库连接使用时就创建,不使用时便立即释放,从而对数据库进行频繁的操作,导致资源的浪费、影响性能。

​ 优化设想<使用数据库连接池管理数据库对象>

  1. sql都是硬编码到Java程序中,如果改变sql,那么得重新编译Java代码,不利于系统后期的维护。

​ 优化设想<将sql语句配置在xml中>,即使改变sql也不用重新编译源代码。

  1. 向PreparedStatement设置参数,也是硬编码到Java程序中,不利于后期的维护。

​ 优化设想<将sql语句以及占位符和参数全部配置在xml中>,改动也不需要重新编译源代码。

  1. 从resultset遍历结果集数据时,也存在硬编码,不利于后期系统的维护。

    优化设想<将查询的结果集自动映射成java对象>;

针对以上问题,顺其自然的就出现了许多优化JDBC的方案,也就是后期出现的ORM持久层框架,例如 Mybatis以及Hibernate等等;这也就是为什么在实际开发中都比较喜欢用ORM框架的原因了。

2. ORM介绍#

在实际开发过程中,我们一般使用ORM框架来代替传统的JDBC,例如Mybatis或者Hibernate,但JDBC 是Java用来实现数据访问的基础,掌握它对于我们理解Java的数据操作流程很有帮助。

ORM<对象关系映射>(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与 关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映 射的元数据,将程序中的对象自动持久化到关系数据库中。

image-20210320102856166

Java典型的ORM中间件有,Mybatis。

ORM技术特点:

  • 提高了开发效率,由于ORM可以自动对Entity对象与数据库中的Table进行字段与属性的映射,所 以我们实际可能已经不需要一个专用的、庞大的数据访问层。
  • ORM提供了对数据库的映射,不用sql直接编码,能够像操作对象一样从数据库获取数据。

3. 什么是MyBatis#

image-20240310205250929

MyBatis 本是 Apache 的一个开源项目 iBatis,2010年这个项目由Apache Software Foundation迁移到 了Google Code,并且改名为MyBatis。

iBATIS一词来源于"internet"和"abatis"的组合,是一个基于Java的持久层框架,iBATIS提供的持久层框

架包括SQL Maps和Data Access Objects(DAOs)。

MyBatis是一个优秀的持久层框架,它对JDBC操作数据库的过程进行封装,使开发者只需要关注 SQL本 身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结 果集检索等JDBC繁杂的过程代码。

MyBatis通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 Java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由MyBatis框架执行sql并将结果 映射成Java对象并返回。

4. MyBatis的优点#

简单易学:

  • 本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件 + 配置几个sql映射文件, 易于学习,易于使用。
  • 通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

灵活:

  • Mybatis不会对应用程序或者数据库的现有设计强加任何影响,sql写在xml里,便于统一管理和优 化。
  • 通过sql语句可以满足操作数据库的所有需求。

解除sq与程序代码的耦合:

  • 通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测 试。
  • sql和代码的分离,提高了可维护性。

提供丰富且强大的标签:

  • 提供映射标签,支持对象与数据库的orm字段关系映射。
  • 提供对象关系映射标签,支持对象关系组建维护。
  • 提供xml标签,支持编写动态sql。

5. MyBatis架构#

image-20240310211122033

1、MyBatis配置:

  • mybatis-config.xml(名称不固定),此文件作为MyBatis的全局(核心)配置文件,配置了 MyBatis的运行环境等信息。
  • mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在 mybatisconfig.xml中加载。

2、通过MyBatis环境等配置信息构造SqlSessionFactory,即会话工厂。

3、由会话工厂创建SqlSession即会话,操作数据库需要通过SqlSession进行。

4、MyBatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行 器、一个是缓存执行器。

5、MappedStatement也是MyBatis一个底层封装对象,Mybatis将SQL的配置信息加载成为一个个 MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存 中。mapper.xml文件中一个sql对应一个MappedStatement对象,sql的id即是MappedStatement的 id。

6、MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、字符串类型、实体类 类型,Executor通过MappedStatement在执行sql前将输入的Java对象映射至sql中,输入参数映射就是 JDBC编程中对PreparedStatement设置参数。

7、MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、字符串类型、实体类 类型,Executor通过MappedStatement在执行sql后将输出结果映射至Java对象中,输出结果映射过程 相当于JDBC编程中对结果的解析处理过程。

三,mybatis的入门案例#

  1. 创建java项目,导入mybatis 和 mysql的jar包
<dependencies>
	<dependency>
		<groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
  1. 创建数据库和表,并编写java的实体类

image-20210320111701686

public class User {
 private long id;
 private String name;
 private String sex;
 private long age;
  // 省略了公共的get和set方法
}
  1. 导入mybatis的核心配置文件 (resources)

在src/main/resources目录下创建mybatis-config.xml

作用<配置了数据源>、事务等MyBatis运行环境等

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDConfig3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
   <!-- 连接数据库的环境配置 default : 默认使用的环境名称 -->
   <environments default="aa">
       <!-- 当前环境 id : 环境名称 唯一 -->
       <environment id="aa">
          <!-- 使用JDBC的事务管理 type : 事务管理器类型 -->
          <transactionManager type="JDBC" />
          <!-- 使用JDBC的连接池 type : pooled 数据库连接池 -->
<dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/mybatisdb" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
        <!-- 引入映射文件 -->
        <mappers>
            <mapper resource="mapper/UserMapper.xml"/>
        </mappers>
</configuration>
  1. 再映射文件中编写映射数据

再resources的文件下创建一个mapper文件夹,把映射文件放入进去

建议名称: xxxMapper.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文件进行分类管理,用于隔离sql语句。
   注意:如果使用mapper代理的方式进行开发,namespace有特殊的作用
-->
<mapper namespace="UserMapper">
   <!-- 专门用来封装自定义的sql语句 -->
   <!--
      通过<select>标签编写查询语句
      id: 映射文件中SQL语句的唯一标识。
          mybatis会将SQL语句封装到MappedStatement对象中,所以此处的id也可以标识
          MappedStatement对象的id;
      注意:同一个mapper文件中id不能重复,而且id在mapper代理模式下有着重要作用;
      parameterType: 输入参数的类型。
          sql语句的占位符:#{};
          #{empno}:其中empno表示接收输入的参数值,参数名称为empno;
          但是如果参数的类型为简单类型(基本数据类型、包装类、字符串类型),参数名称可以任意
指定;
      resultType: 输出参数的类型。
          需要指定输出数据为Java中的数据类型(实体类的全限定名)
    -->
   <select id="selectCount" resultType="int">
      select count(1) from user
   </select>
   <select id="selectAll" resultType="cn.hd.entity.User">
      select * from user
   </select>

去除XML黄色背景:https://blog.csdn.net/u010318957/article/details/72459183

  1. 加载映射文件

需要再mybatis的核心配置文件中读取该映射文件

<!-- 引入映射文件 -->
<mappers>
   <mapper resource="mapper/UserMapper.xml"/>
</mappers>
  1. 测试
public class TestMybatis {
   @Test
   public void t1() throws Exception{
       // 1,读取mybatis的核心配置文件
       InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
       // 2,使用sqlSessionFctoryBuild来创建工厂对象
       SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
       // 3,使用工厂对象来获取sqlSession对象 (类似与preparedStatement)
       SqlSession sqlSession = factory.openSession();
       // 4,使用sqlsession对象来调用其映射文件中的自定义的sql语句执行即可
       /**
       * 查询单条记录
       * selectOne(String statementId, Object param)
       * 参数1:映射文件中的statementId,命名空间名.statementId
       * 参数2:向sql语句中传入的数据,注意:传入的数据类型必须与映射文件中配置的
parameterType保持一致
       * 返回值:就是映射文件中配置的resultType的类型
       * 查询多条记录
       * selectList()
       */
       int count = sqlSession.selectOne("UserMapper.selectCount");
       System.out.println("数据总条数:"+ count);
       // 5,释放资源
       sqlSession.close();
   }
   // 查询所有的数据
   @Test
   public void test3() throws Exception{
       InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
       SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
       SqlSession sqlSession = factory.openSession();
       List<User> list = session.selectList("mapper.UserMapper.selectAll");
       System.out.println(list.toString());
   }
}

四,项目优化#

1. 通用的MybatisUtils工具类#

SqlSessionFactoryBuilder

  • SqlSessionFactoryBuilder是MyBatis中用于创建SqlSessionFactory的构建器,它负责解析 mybatis-config.xml配置文件中的配置信息,并创建出SqlSessionFactory对象。
    • 解析mybatis-config.xml配置文件 SqlSessionFactoryBuilder首先读取mybatis-config.xml配置文件,解析其中的配置信息。
    • 创建Configuration对象 SqlSessionFactoryBuilder创建Configuration对象,并将解析出的配置信息设置到 Configuration对象中。
    • 创建SqlSessionFactory对象 SqlSessionFactoryBuilder根据Configuration对象创建SqlSessionFactory对象,并将其返回 给调用者。
    • 返回SqlSessionFactory对象 SqlSessionFactoryBuilder将创建出的SqlSessionFactory对象返回给调用者,供应用程序使 用
  • SqlSessionFactoryBuilder一般只在应用程序初始化时使用一次,创建出SqlSessionFactory对象 后,就不再需要SqlSessionFactoryBuilder对象
  • 用过即丢,推荐作用域范围<方法体内>

SqlSessionFactory

  • SqlSessionFactory 是MyBatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜 像。
  • 每一个MyBatis的应用程序都以一个 SqlSessionFactory 对象的实例为核心。
  • SqlSessionFactory 也是线程安全的, SqlSessionFactory 一旦被创建,应该在应用执行期间 都存在。在应用运行期间不要重复创建多次,建议使用单例模

SqlSession

  • SqlSession 是MyBatis的关键对象,是执行持久化操作的独享,类似于JDBC中的 Connection
  • 每个线程都应该有它自己的 SqlSession 实例。 SqlSession 的实例不能被共享,同时 SqlSession 也是线程不安全的。
  • 使用完 SqlSeesion 之后关闭Session很重要
  • 线程级,一个request一个 SqlSeesion

总结: 使用单例模式来创建唯一的sqlSessionFactory对象,提高mybatis的执行效率

// 单例模式 : 该类永远只能创建唯一的一个对象
public class MyFactoryUtil {
   // 1,定义一个静态的,私有的 sqlSessionFactory对象
   private static SqlSessionFactory factory = null;
   // 2,提供静态的代码块,来创建唯一的工厂对象
   static{
       try { // ctrl + alt + T
          InputStream is = Resources.getResourceAsStream("mybatis-
config.xml");
          factory = new SqlSessionFactoryBuilder().build(is);
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
   // 3,提供一个公共的静态的方法,来返回factory的唯一对象
   public static SqlSessionFactory getInstance(){
       return factory;
   }
}

2. 优化连接数据库的四大组件#

MyBatis的全局配置文件mybatis-config.xml,配置内容顺序如下:

  • properties(属性)
  • settings(全局配置参数)
  • typeAliases(类型别名)
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境集合属性对象)
    • environment(环境子属性对象)
      • transactionManager(事务管理)
      • dataSource(数据源)
  • mappers(映射器)

方式一:

<!-- 连接数据库的环境配置 default : 默认使用的环境名称 -->
   <environments default="aa">
       <!-- 当前环境 id : 环境名称 唯一 -->
       <environment id="aa">
           <!-- 使用JDBC的事务管理 type : 事务管理器类型 -->
           <transactionManager type="JDBC" />
           <!-- 使用JDBC的连接池 type : pooled 数据库连接池 -->
           <dataSource type="POOLED">
              <property name="driver" value="com.mysql.cj.jdbc.Driver" />
              <property name="url"
value="jdbc:mysql://localhost:3306/mybatisdb" />
              <property name="username" value="root" />
              <property name="password" value="root" />
           </dataSource>
       </environment>
   </environments>

缺点<直接把四大组件信息全部拼接再value中>,不利于重用

<!-- 自定义全局的属性 -->
   <properties>
       <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
       <property name="user" value="root"/>
   </properties>
   <!-- 连接数据库的环境配置 default : 默认使用的环境名称 -->
   <environments default="aa">
       <!-- 当前环境 id : 环境名称 唯一 -->
       <environment id="aa">
           <!-- 使用JDBC的事务管理 type : 事务管理器类型 -->
           <transactionManager type="JDBC" />
           <!-- 使用JDBC的连接池 type : pooled 数据库连接池 -->
           <dataSource type="POOLED">
               <property name="driver" value="${driver}" />
               <property name="url"
value="jdbc:mysql://localhost:3306/mybatisdb" />
               <property name="username" value="${user}" />
               <property name="password" value="root" />
           </dataSource>
       </environment>
       <environment id="bb">
           <!-- 使用JDBC的事务管理 type : 事务管理器类型 -->
           <transactionManager type="JDBC" />
           <!-- 使用JDBC的连接池 type : pooled 数据库连接池 -->
           <dataSource type="POOLED">
               <property name="driver" value="${driver}" />
               <property name="url"
value="jdbc:mysql://localhost:3306/springdemo" />
               <property name="username" value="${user}" />
               <property name="password" value="root" />
           </dataSource>
       </environment>
   </environments>

缺点: 定义为全局的属性,不利于后期的修改和维护

方式三:

把四大组件信息,全部提取出来,放在一个db.properties的文件中,再mybatis的核心配置文件引 入,并使用${key}来动态的引入,便于后期的更改和维护.

db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///mybatisdb
user=root
password=root

mybatis.xml

<!-- 引入外部文件db.properties -->
<properties resource="db.properties"/>
<!-- 连接数据库的环境配置 default : 默认使用的环境名称 -->
<environments default="aa">
   <!-- 当前环境 id : 环境名称 唯一 -->
   <environment id="aa">
       <!-- 使用JDBC的事务管理 type : 事务管理器类型 -->
       <transactionManager type="JDBC" />
       <!-- 使用JDBC的连接池 type : pooled 数据库连接池 -->
       <dataSource type="POOLED">
           <property name="driver" value="${driver}" />
           <property name="url" value="${url}" />
           <property name="username" value="${user}" />
           <property name="password" value="${password}" />
       </dataSource>
   </environment>
</environments>

3. 优化实体类再映射文件中的使用#

方式一:

直接再映射文件中的resultType中定义实体类的全类名(包名.类名)

<!-- 查询所有的数据 List<User> -->
<select id="selectAll" resultType="cn.hd.entity.User">
   select * from user
</select>

缺点: 全类名每一次都直接引入,编写较为麻烦

方式二:

再mybatis的核心配置文件中,使用typeAliases来为每一个实体类取别名

<!-- 取别名 -->
<typeAliases>
   <!-- type : 全类名 alias : 别名-->
   <typeAlias type="cn.hd.entity.User" alias="user"/>
</typeAliases>

缺点: 要为每一个实体类取别名,如果实体类较多,则取别名就较为麻烦

方式三:

再mybatis的核心配置文件中,可以采用以扫描包的形式来取别名.

TIP

实体类一定要放在统一的包中

POJO Ordinary Java Object,简单无规则java对象。

一般只有属性字段,无参构造以及get和set方法,也是指那些没有从任何类继承、也没有实现任何接 口,更没有被其它框架侵入的Java对象。因此它特别灵活可扩展,可以实现让一个模型贯穿多个层,简 单来说可以理解成不包含业务逻辑的单纯用来存储数据的Java类。

Entity<实体类对象>

实体和数据表一一对应,一个实体一张表。

注<关于区分pojo和entity>,查了一些资料也没发现有特别大的差别,功能上来说差别不大,只能从含 义上大致区分一下。不过现在大都是用entity来作为一个表映射的类。

<!-- 取别名 -->
<typeAliases>
   <!-- 扫描包,会把该包中的所有的类全部自动取别名,别名为类名(首字母小写) -->
   <package name="cn.hd.entity"/>
</typeAliases>

MyBatis默认支持别名

别名映射的类型别名映射的类型
_bytebytebytejava.lang.Byte
_shortshortshortjava.lang.Short
_intintintjava.lang.Integer
_integerintintegerjava.lang.Integer
_longlonglongjava.lang.Long
_floatfloatfloatjava.lang.Float
_doubledoubledoublejava.lang.Double
_booleanbooleanbooleanjava.lang.Boolean
stringjava.lang.Stringdatejava.util.Date
mapjava.util.Maphashmapjava.util.HashMap
listjava.util.Listarraylistjava.util.ArrayList
objectjava.lang.Objectobjectjava.lang.Object

4. 引入log4j日志#

导入log4j的jar包

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

再resources中引入log4j的配置文件

log4j.properties

#井号表示注释,配置内容为键值对格式,每行只能有一个键值对,键值对之间以=连接
#指定logger
#设定log4j的日志级别和输出的目的地
#INFO日志级别,Console和logfile输出的目的地
#等级 OFF,ERROR,WARN,INFO,DEBUG,TRACE,ALL
log4j.rootLogger=DEBUG,CONSOLE,file
log4j.logger.cn.smbms.dao=debug
log4j.logger.com.ibatis=debug
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=debug
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=debug
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug
log4j.logger.java.sql.ResultSet=debug
log4j.logger.org.tuckey.web.filters.urlrewrite.UrlRewriteFilter=debug
#指定appender
#设定Logger的Console,其中Console为自定义名称,类型为控制台输出
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=error
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern= [%p] %d %c - %m%n
################################################################################
######
# %d 输出日志的日期和时间,指定格式:%d{yyyy-MM-dd HH:mm:ss SSS}
# %p 输出的日志级别
# %c 输出所属类的全类名
# %M 方法名
# %m 输出代码中指定消息
# %n 一个换行符
################################################################################
######
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern=yyyy-MM-dd
log4j.appender.file.File=log.log
log4j.appender.file.Append=true
log4j.appender.file.Threshold=error
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L)
%m%n

log4j.logger.com.opensymphony.xwork2=error

再核心配置文件中使用setting来引入log4j

<!-- 引入log4j日志 -->
<settings>
   <setting name="logImpl" value="LOG4J"></setting>
</settings>
/**
* 日志测试
*/
public class LogTest {
   /*
   * 我们之前想要查看程序的执行信息,都是通过System.out输出,这样显然是不方便的
   * 1、内容格式需要自己设置
   * 2、项目上线之后,不方便查看
   */
   @Test
   public void beforeLogTest(){
        System.out.println("常规日志输出...");
   }
   /*
   * Java自带的日志输出
   * 晚于log4j,用的很少
   */
   @Test
   public void julTest(){
       java.util.logging.Logger logger =
       java.util.logging.Logger.getLogger(LogTest.class.getName());
       logger.info("jul日志输出...");
   }
   @Test
   public void log4jTest(){
       org.apache.log4j.Logger logger =
       org.apache.log4j.Logger.getLogger(LogTest.class);
       logger.trace("trace日志...");
       logger.debug("debug日志...");
       logger.info("info日志...");
       logger.warn("warn日志...");
       logger.error("error日志...");
   }
}

5. 引入Mapper代理模式#

方式一:原始DAO开发

在session调用sql时,根据namespace+id的字符串形式找到sql语句并执行

图片1

缺点:

  • 调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护。
  • 调用SqlSession方法时传入的变量,由于SqlSession方法使用泛型,即使变量类型传入错误,在编 译阶段也不报错,不利于程序员开发。

方式二:Mapper代理模式

Mapper代理开发方式只需要程序员编写Mapper接口(相当于Dao接口),由MyBatis框架根据接口定 义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

程序员编写Mapper接口需要遵循一些开发规范,MyBatis可以自动生成Mapper接口实现类代理对象。

开发规范

    1. Mapper.xml文件中的namespace与mapper接口的类路径相同。
    1. Mapper.xml中定义的每个标签的id与Mapper接口方法名相同。
    1. Mapper.xml中定义的每个sql的parameterType的类型与Mapper接口方法的参数类型相同。
    1. Mapper.xml中定义的每个sql的resultType的类型与Mapper接口方法返回值类型相同。

.xml映射文件最好和Mapper接口名称一致

  1. 在java中新创建一个mapper包,并创建接口(xxxMapper.java)。【例如.hs.mapper】

image-20240310222056575

  1. 在对应的resource文件夹中创建一个与接口一样的多级文件夹,并创建映射文件,要求映射文件名 必须和接口名重名.(xxxMapper.xml) 。 【例如/hs/mapper】

创建文件夹

image-20240310222207698

创建映射文件

image-20240310222551915

  1. 为配置定义接口方式来实现
  • Mapper映射文件的namespace必须是 对应的mapper的接口的全类名

  • Mapper映射文件的id必须对应的接口中的方法名

  • Mapper映射文件的resultType必须对应的时接口中的返回值类型或泛型

  • Mapper映射文件的paramtertype必须对应的时接口中的参数的类型

image-20240310222859915

  1. 在核心配置文件中配置 映射文件的新读取方式

image-20240310223310413

  1. 测试:
@Test
public void t2()throws IOException{
    SqlSessionFactory factory = MyBatisUitls.getFactory();
    SqlSession session = factory.openSession();
    StudentMapper mapper=session.getMapper(StudentMapper.class);
    List<Student> list = mapper.selectStudents();
    for(Student s:list){
        System.out.println(s.toString()
    )
    session.close();
}

6. 对实体类中基本数据类型的优化#

如果表中字段时基本数据类型,实体类也是基本数据类型时,如果表中的数据是null,则查询时就 好报错(null不能放入到实体类中的基本数据类型的属性中);

解决<以后所有的实体类中的基本数据类型全部改为其包装类>

NOTE

实体类中的所有的属性名 与 表中的所有的字段名一一对应

五,常用方法#

1、mybatis实现常见的CRUD功能#

增加 删除 修改 查询所有 根据id查询 模糊查询 多条件查询 查询字段与实体类属性不匹配

注意:

mybaits的占位符和赋值组合 : #{ 属性名 } ? ps.setObject(1,student.getName())

增删改时需要手动提交事务: session.commit(); // 手动提交事务

StudentMappr.java

public interface StudentMapper {
   // 查询所有的数据
   public List<Student> selectAll();
   // 添加数据
   public int add(Student student);
   // 修改
   public int update(Student student);
   // 删除
   public int del(long id);
   // 根据id查询学生信息
   public Student selectById(long id);
   // 模糊查询
   public List<Student> selectByName(String name);
   /*
   多条件查询: 注意:mybatis默认状态下不能一次性传入多个不同类型的数据
      1,多个条件正好是实体类的直接属性 对象
         根据姓名模糊,性别具体,年龄具体 查询
      2,多个条件不是实体类的直接属性 Map
         根据姓名模糊,和年龄范围查询
      3,多个条件不是实体类的直接属性 xxxVo
         根据年龄范围查询
      4,多个条件不是实体类的直接属性 @param方法来实现一次性传入多条不同的数据
         根据年龄范围查询
   */
  public List<Student> selectByNameAndSexAndAge(Student student);
   public List<Student> selectByNameAndAge(Map map);
   public List<Student> selectByAge(StudentVo studentVo);
   public List<Student> selectByAgeInParam(@Param("min") long
minage,@Param("max") long maxage);
   /*
     数据库表的字段信息与实体类的属性不匹配
       1,sql语句查询时,以取别名的形式实现查询的结果与实体类属性名重名
       2,使用resultMap来实现指定查询数据的赋值
    */
}

StudentMapper.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">
<mapper namespace="cn.hs.mapper.StudentMapper">
   <!-- 使用resultMap来手动的定义 字段之间的赋值 -->
   <resultMap id="stumap" type="student">
       <result column="s_name" property="name"/>
   </resultMap>
   <!-- 查询所有的学生信息 -->
   <select id="selectAll" resultMap="stumap">
       select * from student
   </select>
   <!--<select id="selectAll" resultType="student">
       select id,s_name as name ,age,birthday,address from student
   </select>-->
   <!-- 添加数据
           占位符+赋值: #{ 属性名 }
       useGeneratedKeys : 设置当前的主键为自增方式
       keyProperty: 设置自增后的主键值为当前的对象的属性赋值
        结果: 添加成功后,java中的对象的id会反射到java对象中
   -->
   <insert id="add" parameterType="student" useGeneratedKeys="true"
keyProperty="id">
       insert into student values(null,#{name},#{sex},#{age},#{birthday},#
{address})
   </insert>
   <update id="update" parameterType="student">
       update student set name=#{name},age=#{age},birthday=#
{birthday},address=#{address} where id=#{id}
   </update>
  <delete id="del" parameterType="long">
       delete from student where id = #{id}
   </delete>
   <select id="selectById" parameterType="long" resultType="student">
       select * from student where id = #{id}
   </select>
   <!--
       方式一 : 传入的参数前后加% %张%
       方式二 : 再sql语句中实现前后拼接 % '%' #{name} '%'
       方式三 : 使用${key}模式来实现前后拼接 '%${name}%'
    -->
   <!--<select id="selectByName" parameterType="java.lang.String"
resultType="student">
       select * from student where name like #{name}
   </select>-->
   <!--<select id="selectByName" parameterType="java.lang.String"
resultType="student">
       select * from student where name like '%' #{name} '%'
   </select>-->
   <select id="selectByName" parameterType="java.lang.String"
resultType="student">
       select * from student where name like '%${name}%'
   </select>
   <select id="selectByNameAndSexAndAge" parameterType="student"
resultType="student">
       select * from student
       where name like '%${name}%' and sex=#{sex} and age=#{age}
   </select>
   <!-- 配置文件中的小于一般不能使用
           1.使用between .. and ...
               select * from student
               where name like '%${name}%' and age between #{minage} and #
{maxage}
           2.让占位符和字段进行反转即可
               select * from student
               where name like '%${name}%' and age >= #{minage} and #{maxage}
>= age
           3.使用 < < > > <= <= >= >=
               select * from student
               where name like '%${name}%' and age > #{minage} and age
< #{maxage}
    -->
   <select id="selectByNameAndAge" parameterType="java.util.Map"
resultType="student">
       select * from student
       where name like '%${name}%' and age >= #{minage} and #{maxage} >= age
   </select>
   <select id="selectByAge" parameterType="studentVo" resultType="student">
       select * from student where age between #{minage} and #{maxage}
   </select>
  <select id="selectByAgeInParam" resultType="student">
        select * from student where age between #{min} and #{max}
    </select>
</mapper>

2、resultMap的简单用法#

resultType可以指定将查询结果映射为实体类,但需要实体类的属性名和SQL查询的列名一致方可映射成功,当然如果开启下划线转驼峰 或 Sql设置列别名,也可以自动映射。

<!-- 开启下划线与驼峰命名的转换:-->
<settings>
   <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
  • 如果SQL查询字段名和实体类的属性名不一致,可以通过resultMap将字段名和属性名作一个对应 关系,resultMap实质上还会将查询结果映射到实体类对象中。
  • resultMap可以实现将查询结果映射为复合型的实体类,比如在查询结果映射对象中包括实体类和 list实现一对一查询和一对多查询。
<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id"/>
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password from users
</select>

resultMap常用的属性

resultType: 自动映射。

resultMap: 手动映射。

  • id: 唯一标识,名称;
  • type: 手动映射的java类型;
  • 子标签 < id/> 配置数据库表中的主键和实体类中属性的对应关系;
  • 子标签 < result/> 配置数据库表中的普通字段和实体类中属性的对应关系;
    • property<实体类中的成员变量名>;
    • column<结果集中的字段名称>;
    • javaType<实体类成员变量的类型>,由mybaits自动识别,可不配置;
    • jdbcType<表字段的类型>,由mybaits自动识别,可不配置;
    • typeHandler<自定义类型处理器>,用的相对比较少;

在resultMap标签中,可使用和子标签指定一个属性名跟字段名的映射,property是实体类的属性名, column是数据库中的字段名。子节点一般对应该记录行的数据库主键字段,可提升MyBatis性能,二者 作用差不多。

resultMap的自动映射级别:

PARTIAL,默认值,自动匹配基本类型

NONE,不进行自动匹配

FULL,自动匹配所有类型,包括复杂的JavaBean、引用类型。

一般设置为自动匹配所有,这样只需要在resultMap中配置名称不对应的属性即可,其他不做手动映射 的属性依然可以映射上。此选项在mybatis-config.xml(核心配置文件)中的settings标签中设置,代码 如下

<!--设置为FULL,自动匹配所有,则在resultMap中不进行匹配的字段也可以映射-->
<setting name="autoMappingBehavior" value="FULL"/>

六,分页#

之前的分页都是以mysql中的limit关键字实现分页。Page

MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封 装,使用简单的方式即可获得分页的相关数据

开发步骤:

①导入通用PageHelper的坐标

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.2</version>
</dependency>

②在mybatis核心配置文件中配置PageHelper插件

<plugins>
   <plugin interceptor="com.github.pagehelper.PageInterceptor">
       <!-- 方言 默认是mysql方言 -->
       <property name="helperDialect" value="mysql"/>
   </plugin>
</plugins>

③测试分页数据获取

@Test
public void fenye(){
   SqlSessionFactory factory = MyFactoryUtil.getInstance();
   SqlSession sqlSession = factory.openSession();
   StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
   PageHelper.startPage(2,2);
   List<Student> students = studentMapper.selectAll();
   for(Student student : students){
       System.out.println(student.toString());
   }
   // 动态获取分页后相关的数据参数
   PageInfo<Student> pageInfo = new PageInfo<>(students);
   System.out.println(pageInfo.getTotal()); // 总条数
   System.out.println(pageInfo.getPageNum()); // 页码
   System.out.println(pageInfo.getPages()); // 总页数
   System.out.println(pageInfo.getPageSize()); // 每页展示条数
   System.out.println(pageInfo.getList()); // 数据集合
   sqlSession.close();
}

注意:

再开发中,如果需要多条件查询并分页,我们只需要提供多条件查询的语句即可,再查询前加入 pageHelper即可,案例如下:

// 多条件查询的语句
   <select id="selectByNameAndAage" resultType="student">
       select * from student
       where name like '%' #{name} '%' and age between #{min} and #{max}
   </select>
// 分页测试
   @Test
   public void home(){
       SqlSessionFactory factory = MyFactoryUtil.getInstance();
       SqlSession sqlSession = factory.openSession();
       StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
       PageHelper.startPage(1,2); // 加入分页即可实现多条件查询并分页效果
       List<Student> students = mapper.selectByNameAndAage("无", 10l, 30l);
       for(Student student : students){
           System.out.println(student.toString());
       }
       sqlSession.close();
   }

七,动态sql#

再真实的开发中,会出现根据多条件动态查询,动态的修改,批量删除等功能,mybatis也提供了一套 专门的动态sql来实现这些效果.

动态Sql是指MyBatis对Sql语句进行灵活操作,通过表达式进行判断,对Sql进行灵活拼接、组装。

1. <where> 标签#

作用:用于替代 SQL 中的 WHERE 关键字,能够智能地处理 AND/OR 条件。

特点

  • 只有当子元素返回内容时才会插入 WHERE 子句
  • 会自动去除开头多余的 AND 或 OR

示例

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         AND state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
  </where>
</select>

2. <if> 标签#

作用:条件判断,根据表达式的值决定是否包含其中的 SQL 片段。

特点

  • 使用 OGNL 表达式进行判断
  • 通常与其他动态 SQL 标签配合使用

示例

<if test="title != null">
    AND title = #{title}
</if>

3. <set> 标签#

作用:用于 UPDATE 语句,智能处理 SET 子句。

特点

  • 自动去除结尾的逗号
  • 只有当子元素返回内容时才会插入 SET 子句

示例

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
    </set>
  where id=#{id}
</update>

4. <foreach> 标签#

作用:遍历集合,常用于 IN 条件或批量操作。

属性

  • collection:要遍历的集合
  • item:当前元素变量名
  • index:索引变量名
  • open/close:开始/结束字符串
  • separator:元素间分隔符

示例

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT * FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

5. <choose><when><otherwise> 标签#

作用:实现类似 Java 中的 switch-case 逻辑。

特点

  • <choose> 作为父标签
  • <when> 表示 case 条件
  • <otherwise> 表示默认情况

示例

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = 'ACTIVE'
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

案例#

StudentMapper.java

public interface StudentMapper {
   // 根据姓名模糊,年龄范围
   public List<Student> selectByNameAndAage(@Param("name")String
name,@Param("min")long minage,@Param("max")long maxage);
   // 动态sql的查询
   public List<Student> select(Map map);
   // 修改
   public int update(Student student);
   // 批量删除
   public int del(List list);
}

StudentMapper.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">
<mapper namespace="com.mapper.StudentMapper">
   <select id="selectByNameAndAage" resultType="student">
       select * from student
       where name like '%' #{name} '%' and age between #{min} and #{max}
   </select>
   <!-- where + if 组合--->
   <select id="select" parameterType="java.util.Map" resultType="student">
       select * from student
       <where>
           <if test="name!=null and name!=''">
               name like '%${name}%'
           </if>
           <if test="sex!=null and sex!=''">
               and sex = #{sex}
           </if>
           <if test="minage!=null and minage!=''">
               and age between #{minage} and #{maxage}
           </if>
       </where>
   </select>
   <!--<update id="update" parameterType="student" >
       update student set name=#{name},sex=#{sex},age=#{age},birthday=#
{birthday},address=#{address}
       where id=#{id}
   </update>-->
   <!-- set + if 组合--->
   <update id="update" parameterType="student" >
       update student
       <set>
           <if test="name!=null and name!=''"> name=#{name}, </if>
           <if test="sex!=null and sex!=''"> sex=#{sex}, </if>
           <if test="age!=null and age!=''"> age=#{age}, </if>
           <if test="birthday!=null and birthday!=''"> birthday=#{birthday},
</if>
           <if test="address!=null and address!=''"> address=#{address} </if>
       </set>
       where id=#{id}
   </update>
   <!-- foreach 组合
       collection: 遍历的数组或集合对象名称。
       SQL只接收一个数组参数,这时SQL解析参数的名称MyBatis固定为array。
			 SQL只接收一个List参数,这时SQL解析参数的名称MyBatis固定为list。
       如果是通过一个实体类或自定义类型的属性传递到SQL的数组或List集合,则参数的名称为实
       体类或自定义类型中的属性名。
       index: 为数组的下标。
       item: 每次遍历生成的对象。
       open: 开始遍历时拼接的串。
       close: 结束遍历时拼接的串。
       separator: 遍历的两个对象中需要拼接的串。
--->
   <delete id="del" parameterType="java.util.List">
       delete from student where id in
       <foreach collection="list" item="i" open="(" close=")" separator=",">
          #{i}
       </foreach>
   </delete>
   <!-- choose + when 组合--->
   <select id="selectByTJ" parameterType="student" resultType="student">
       select * from dept where
       <choose>
          <when test="id!=null and id!=''">
              deptno = #{deptno}
          </when>
          <when test="name!=null and name!=''">
              name like #{name}
          </when>
          <otherwise>
              1 = 1
          </otherwise>
       </choose>
   </select>
</mapper>

测试:

public class TestMybatis {
    @Test
    public void del(){
        SqlSessionFactory factory = MyFactoryUtil.getInstance();
        SqlSession sqlSession = factory.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List list = new ArrayList();
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        int num = mapper.del(list);
        sqlSession.commit();
        sqlSession.close();
    }
  @Test
  public void update(){
      SqlSessionFactory factory = MyFactoryUtil.getInstance();
      SqlSession sqlSession = factory.openSession();
      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
      Student student = new Student();
      student.setId(1L);
      student.setAge(98L);
      student.setAddress("河南省商丘市");
      int num = mapper.update(student);
      sqlSession.commit();
      sqlSession.close();
  }
  @Test
  public void select(){
      SqlSessionFactory factory = MyFactoryUtil.getInstance();
      SqlSession sqlSession = factory.openSession();
      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
      PageHelper.startPage(1,2);
      Map map = new HashMap();
      //map.put("name","张");
      map.put("sex","男");
      map.put("minage",20);
      map.put("maxage",200);
      List<Student> students = mapper.select(map);
      for(Student student : students){
          System.out.println(student.toString());
      }
      sqlSession.close();
  }
  @Test
  public void home(){
      SqlSessionFactory factory = MyFactoryUtil.getInstance();
      SqlSession sqlSession = factory.openSession();
      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
      PageHelper.startPage(1,2);
      List<Student> students = mapper.selectByNameAndAage("无", 10l, 30l);
      for(Student student : students){
          System.out.println(student.toString());
      }
      sqlSession.close();
  }
}

八,sql片段#

将实现的动态SQL判断代码块抽取出来,组成一个SQL片段,其它的statement中就通过 < include> 标 签就可以引用SQL片段,方便程序员进行开发。

在项目中有时会发现该表的操作中有很多的多条件查询,每一个条件查询的前半部分是相同,则我们可 以使用sql包含来实现一个编写一次,多次调用的方式。

<!-- 定义一个sql,来定义其通用的查询的前半部分 -->
<sql id="sl">
       select id,name,sex,age from student
</sql>
<!-- 在下方的sql语句查询中引用通用的部分sql代码 -->
<select id="selectByLikeName3" parameterType="java.lang.String"
resultType="student">
       <include refid="sl"></include>
       where name like '%${name}%'
   </select>
   <select id="selectByNameAndAge" parameterType="student"
resultType="student">
       <include refid="sl"></include>
       where name like '%${name}%' and age=#{age}
   </select>

九,注解开发#

Mybatis最初配置信息是基于XML,映射语句(SQL)也是定义在 XML 中的。而到了MyBatis3提供了新 的基于注解的配置。使用注解开发方式,可以减少编写 Mapper 映射文件。

常用注解说明

image-20240310230312555

1. 单表操作#

StudentInfoMapper.java

public interface StudentInfoMapper {
    @Insert("insert into student values(null,#{name},#{sex},#{age},#{birthday},#
{address})")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn =
"id")
    public int add(Student student);
    @Update("update student set name=#{name},sex=#{sex},age=#{age},birthday=#
{birthday},address=#{address} where id=#{id}")
    public int update(Student student);
    @Delete("delete from student where id =#{id}")
    public int delete(long id);
    @Select("select * from student")
    public List<Student> selectAll();
}

注意: 注解模式时,也要再核心配置文件中实现扫描包

<mappers>
    <package name="com.mapper"/>
</mappers>

Mybatis注解开发:https://blog.csdn.net/weixin\_43883917/article/details/113830667

测试:

public class TestMybatisAnition {
    @Test
    public void t4(){
        SqlSessionFactory factory = MyFactoryUtil.getInstance();
        SqlSession sqlSession = factory.openSession();
        PageHelper.startPage(1,2);
        StudentInfoDao mapper = sqlSession.getMapper(StudentInfoDao.class);
        mapper.delete(4l);
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void t3(){
        SqlSessionFactory factory = MyFactoryUtil.getInstance();
        SqlSession sqlSession = factory.openSession();
        PageHelper.startPage(1,2); // 查询数据并分页
        StudentInfoDao mapper = sqlSession.getMapper(StudentInfoDao.class);
        List<Student> students = mapper.selectAll();
        for (Student student : students){
        	System.out.println(student.toString());
        }
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void t2(){
        SqlSessionFactory factory = MyFactoryUtil.getInstance();
        SqlSession sqlSession = factory.openSession();
        StudentInfoDao mapper = sqlSession.getMapper(StudentInfoDao.class);
        Student student = new Student(4l,"周芷若","女",20l,new Date(),"北京");
        mapper.update(student);
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void t1(){
        SqlSessionFactory factory = MyFactoryUtil.getInstance();
        SqlSession sqlSession = factory.openSession();
        StudentInfoDao mapper = sqlSession.getMapper(StudentInfoDao.class);
        Student student = new Student(1l,"周芷若","女",19l,new Date(),"北京");
        mapper.add(student);
        sqlSession.commit();
        sqlSession.close();
  	}
}

2. 注解解决字段不重名#

使用@Results 和 @Result 来实现 ,类似ResultMap的写法

// 查询的结果字段与实体类的属性不重名
   @Select("select id,name as sname,sex,age,birthday,address from student")
   @Results(value = {
           @Result(column = "sname",property = "name"),
           @Result(column = "age",property = "age")
   })

3. 注解实现动态sql#

使用内部类来实现,方法中返回的是new SQL() , org.apache.ibatis.jdbc.SQL包

// 动态sql(基于内部类来实现) 根据name模糊,根据sex具体
// type : 自定义的内部类 method : 内部类的方法实现
 @SelectProvider(type = namesex.class,method = "csql")
 public List<Student> selectByNameAndSex(Map map);
// 定义一个内部类,再内部类中定义一个方法( String返回值 map集合参数 )来生成动态sql语句
class namesex{ // select id,name,sex from 表名 where ... and ...
   public String csql(Map map){
       String sql = new SQL(){{
           SELECT(" id,name,sex ");
           SELECT("age,birthday,address");
           FROM("student");
           if(map.get("name")!=null && !"".equals(map.get("name"))){
               WHERE(" name like '%${name}%'");
           }
           if(map.get("sex")!=null && !"".equals(map.get("sex"))){
               WHERE(" sex=#{sex}");
           }
       }}.toString();
       return sql;
   }
}

升级:

双层sql语句的动态生成案例:

select count(1) from

(select id,name,sex,age,birthday,addressfrom student where ......) s
@SelectProvider(type =cns.class,method ="csql" )
public int selectCountByNS(Map map);
class cns{
    public String csql(Map map){
        String s1 = new SQL(){{
            SELECT(" * ");
            FROM("student");
            if(map.get("name")!=null){
                WHERE("name like '%${name}%'");
            }
            if(map.get("sex")!=null){
                WHERE(" sex = #{sex}");
            }
        }}.toString();
        String s2 = new SQL(){{
            SELECT("count(1)");
            FROM("("+s1+") as s");
        }}.toString();
        return s2;
    }
}

升级:

根据id批量删除

delete from student where id in (......);

注意: mybatis3.5.5版本以上,内部类的方法中可以传入list集合,以下版本只能传入Map集合

@DeleteProvider( type =delClass.class ,method = "csql")
    public int deletes(List list);
    class delClass{
    public String csql(List list){ // 低版本的以map集合包裹list传入
        String sql = new SQL(){{
            DELETE_FROM("student");
            String s = "id in(";
            for(int i=0;i<list.size();i++){
                Object obj = list.get(i);
                if(i==list.size()-1){
                    s+=obj+")";
                }else{
                    s+=obj+",";
                }
            }
            WHERE(s);
        }}.toString();
        return sql;
    }
}

4. @MapperScan#

在入口处添加@MapperScan

@SpringBootApplication
@MapperScan("fun.masttf.mapper")
public class testApplication {
    public static void main(String[] args) {
        SpringApplication.run(testApplication.class, args);
    }
}

配置说明

  1. 使用 @MapperScan 的优势:
    • 简化配置,无需创建 SqlSessionFactory、SqlSessionTemplate 等 bean
    • 自动扫描指定包下的所有接口作为 Mapper 组件注册到 Spring 容器
    • 与 Spring Boot 的自动配置机制完美结合

@MapperScan 的作用

  1. Spring 组件注册@MapperScan 将指定包下的所有 Mapper 接口自动注册为 Spring Bean,使它们能够被自动装配(@Autowired)到其他组件中
  2. 接口实现类生成:MyBatis 会为这些接口动态生成实现类,实现与 XML 文件的绑定
  3. 批量扫描:一次性指定整个包,避免为每个 Mapper 接口单独添加 @Mapper 注解

XML 中 namespace 的作用

  1. 映射绑定:将 XML 文件中的 SQL 定义与特定 Java 接口绑定
  2. 运行时关联:MyBatis 运行时根据这个关联找到对应的 SQL 语句

配置文件迁移到application.yml/properties

  • 传统MyBatis的mybatis-config.xml中的核心配置被移至Spring Boot配置文件

  • mybatis:
      # XML文件位置
      mapper-locations: classpath:fun/masttf/mapper/*.xml
      
      # 实体类别名包
      type-aliases-package: fun.masttf.entity.po
      
      # 对应原mybatis-config.xml中的<settings>
      configuration:
        # 驼峰命名自动映射
        map-underscore-to-camel-case: true
        
        # 缓存配置
        cache-enabled: true
        
        # 延迟加载配置
        lazy-loading-enabled: true
        aggressive-lazy-loading: false
        
        # 日志实现
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        
      # MyBatis-Plus特有配置(如果使用)
      global-config:
        db-config:
          id-type: auto
          table-underline: true
    NOTE

    需要添加依赖

    <dependency>
         <groupId>org.mybatis.spring.boot</groupId>
         <artifactId>mybatis-spring-boot-starter</artifactId>
         <version>2.2.0</version>
       </dependency>

十,表与表之间的关系#

表之间的关系: 一对一 一对多 多对多

员工表与部门表

  • 1,查询员工信息的同时查询其所属的部门信息
  • 2,查询部门信息的同时把所属的员工信息也要查询出来

创建表:

create table dept(
   did int PRIMARY key auto_increment,
   dname varchar(20)
)
insert into dept values(null,"市场部");
insert into dept values(null,"教学部");
insert into dept values(null,"研发部");
create table emp(
   eid int PRIMARY key auto_increment,
   ename varchar(20),
   esex varchar(10),
   eage int,
   did int ,
   foreign key(did) REFERENCES dept(did)
)
insert into emp values(null,"张三丰","男",22,1);
insert into emp values(null,"张无忌","男",20,1);
insert into emp values(null,"赵敏","女",22,2);
insert into emp values(null,"周芷若","女",20,1);

1 一对一#

根据员工编号查询员工信息,同时把该员工所属的部门信息也要查询

创建实体类:

public class Emp {
 private long eid;
 private String ename;
 private String esex;
 private long eage;
 private long did;
 // 一对一 查询员工信息的同时把所属的部门信息查到
 private Dept dept;
 // 省略对应的get和set方法
}

编写EmpDao.java

public interface EmpDao {
   // 根据eid来查询员工信息
   public Emp selectByEid(long id);
}

EmpDao.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">
<mapper namespace="com.mapper.EmpDao">
   <resultMap id="emp_dept" type="emp">
       <id column="eid" property="eid"/>
       <result column="ename" property="ename"/>
       <result column="esex" property="esex"/>
       <result column="eage" property="eage"/>
       <result column="did" property="did"/>
       <!-- 一对一 封装 -->
       <association property="dept" javaType="dept">
           <id column="did" property="did"/>
           <result column="dname" property="dname"/>
       </association>
   </resultMap>
   <!-- 查询的结果中,有emp的属性值则自动封装成对象;当时其所属的部门信息不能自动封装,我们就
可以手动实现封装 -->
   <select id="selectByEid" parameterType="long" resultMap="emp_dept">
       select emp.*,dept.did ddid,dept.dname from emp,dept where
emp.did=dept.did and eid=#{eid}
   </select>

测试:

@Test
    public void del(){
        SqlSessionFactory factory = MyFactoryUtil.getInstance();
        SqlSession sqlSession = factory.openSession();
        EmpDao mapper = sqlSession.getMapper(EmpDao.class);
        Emp emp = mapper.selectByEid(1);
        System.out.println(emp.toString());
        sqlSession.close();
    }

优化一

可以再mybatis的核心配置文件中设置 自动装配 ,resultMap中的手动封装数据就会简化

<setting name="autoMappingBehavior" value="FULL"/>

再说一下resultMap的自动映射级别:

PARTIAL,默认值,自动匹配基本类型

NONE,不进行自动匹配

FULL,自动匹配所有类型,包括复杂的JavaBean、引用类型。

<?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.mapper.EmpDao">
   <resultMap id="emp_dept" type="emp">
       <!-- 一对一 封装 -->
       <association property="dept" javaType="dept">
       </association>
   </resultMap>
   <!-- 查询的结果中,有emp的属性值则自动封装成对象;当时其所属的部门信息不能自动封装,我们就
可以手动实现封装 -->
   <select id="selectByEid" parameterType="long" resultMap="emp_dept">
       select emp.*,dept.did ddid,dept.dname from emp,dept where
emp.did=dept.did and eid=#{eid}
   </select>
</mapper>

优化二

可以以单表查询的方法来查询员工信息,在把数据封装中association使用外键来调用另一张表的查询 sql来实现查询对象,并封装到当前的员工信息的dept对象中。

deptDao.java

// 根据did来查询部门信息
   public Dept selectByDid(long did);

deptDao.xml

<select id="selectByDid" parameterType="long" resultType="dept">
     select * from dept where did=#{did}
 </select>

EmpDao.java

// 根据id来查询员工信息及部门信息
   public Emp selectByEid2(long id);

EmpDao.xml

<resultMap id="emp_dept_2" type="emp">
       <id column="eid" property="eid"/>
       <result column="ename" property="ename"/>
       <result column="sex" property="esex"/>
       <result column="eage" property="eage"/>
       <result column="did" property="did"/>
       <!-- 根据外键 去调用另一条查询语句把结果映射到当前的reusltMap中进行 一对一 封装 --
>
       <association property="dept" javaType="dept" column="did"
select="com.mapper.DeptDao.selectByDid"></association>
   </resultMap>
   <select id="selectByEid2" parameterType="long" resultMap="emp_dept_2">
       select * from emp where eid = #{eid}
   </select>

2 一对多#

根据部门编号来查询部门信息,同时把该部门下的所属的所有的员工信息也查询

编写实体类:

public class Dept {
 private long did;
 private String dname;
 // 一个部门下有多个员工信息 一对多
 private List<Emp> emps;
// 省略公共的get和set方法
}

编写DeptDao.java

// 根据编号来查询部门信息同时把该部门下的员工信息也要查到
public Dept selectdeByDid(long did);

DeptDao.xml

// 定义手动封装的方式
<resultMap id="dept_emp" type="dept">
       <id column="did" property="did"/>
       <result column="dname" property="dname"/>
       <!-- collection : 集合 一对多 -->
       <collection property="emps" ofType="emp">
           <id column="eid" property="eid"/>
           <result column="ename" property="ename"/>
           <result column="esex" property="esex"/>
           <result column="eage" property="eage"/>
           <result column="did" property="did"/>
       </collection>
   </resultMap>
   <select id="selectdeByDid" parameterType="long" resultMap="dept_emp">
       select emp.*,dname from dept,emp where dept.did=emp.did and dept.did=1
   </select>

测试:

@Test
    public void sel3(){
        SqlSessionFactory factory = MyFactoryUtil.getInstance();
        SqlSession sqlSession = factory.openSession();
        DeptDao mapper = sqlSession.getMapper(DeptDao.class);
        Dept dept = mapper.selectdeByDid(1);
        System.out.println(dept);
        sqlSession.close();
    }

优化一

可以再mybatis的核心配置文件中设置 自动装配 ,resultMap中的手动封装数据就会简化

<setting name="autoMappingBehavior" value="FULL"/>

再说一下resultMap的自动映射级别:

PARTIAL,默认值,自动匹配基本类型

NONE,不进行自动匹配

FULL,自动匹配所有类型,包括复杂的JavaBean、引用类型。

<resultMap id="dept_emp" type="dept">
   // 特殊定义did的自动注入,如果不写,则查询的结果可能是多行,封装成一个对象不匹配
   <id column="did" property="did"/>
   <!-- collection : 集合 一对多 -->
   <collection property="emps" ofType="emp">
   </collection>
</resultMap>
<select id="selectdeByDid" parameterType="long" resultMap="dept_emp">
   select emp.*,dname from dept,emp where dept.did=emp.did and dept.did=1
</select>

优化二

可以以单表查询的方式先查询部门信息,然后再Collection中的select来调用另一条根据did查询所有 的员工信息的sql语句来查询员工信息,进行集合封装。

EmpDao.java

// 根据did查询员工信息
  public List<Emp> selectByDid(long did);

EmpDao.xml

<select id="selectByDid" parameterType="long" resultType="emp">
        select * from emp where did=#{did}
    </select>

deptDao.java

// 根据编号来查询部门信息同时把该部门下的员工信息也要查到
   public Dept selectdeByDid2(long did);

deptDao.xml

<resultMap id="dept_emp_2" type="dept">
       <id column="did" property="did"/>
       <result column="dname" property="dname"/>
       <!-- 一对多 colum : 外键 select : 另一条查询语句的坐标 -->
       <collection property="emps" ofType="emp"
       column="did" select="com.mapper.EmpDao.selectByDid"></collection>
   </resultMap>
   <select id="selectdeByDid2" parameterType="long" resultMap="dept_emp_2">
       select * from dept where did=#{did}
   </select>

十一,延迟加载#

针对关联查询操作。#

assoation 和 collection

延迟加载#

(1) 懒加载。

依赖的对象,在不需要时,先不加载。如果需要的对象,则立马执行sql语句进行查询。

(2) 分类:

全局延迟加载

局部延迟加载

开启全局延迟加载#

<!--开启延迟加载开关,默认false-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--
在延迟加载的基础上使用:
false按需加载 , 访问指定属性,不会加载其他属性;
true 全部加载 , 访问任意属性,都会加载全部属性;
-->
<setting name="aggressiveLazyLoading" value="true"/>

注意<如果使用中去调用了方法去打印信息>【toString()】

则需要额外加入方法的配置:

<!--指定哪些方法去触发延迟加载,hashCode,equals,clone,toString-->
<setting name="lazyLoadTriggerMethods" value=""/>

局部延迟加载#

  • (1)注释全局加载设置
  • (2)设置局部延迟加载
<association fetchType="lazy" ...
<collection fetchType="lazy" ...

注意:

局部延迟加载的优先级要高于全局延迟加载

总结:

一对一 一般采用立即加载

一对多 多对多 一般采用懒加载

十二,查询缓存#

缓存<将数据临时存储在存储介质>(内存,文件)中,关系型数据库的缓存目的就是为了减轻数据库的 压力。

数据库的数据实际是存储在硬盘中,如果我们程序需要用到数据,那么就需要频繁的从磁盘中读取数 据,效率低,数据库压力大。我们可以把查询到的数据缓存起来,这样就减少了频繁操作磁盘数据,提 高查询效率,减轻服务器压力。

Mybatis提供了查询缓存,用于减轻数据库压力,提高数据库性能。但是在实际项目开发中,很少使用 Mybatis的缓存机制,现在主流的缓存机制是redis。

缓存实现技术:

mybatis默认支持内置的缓存技术

redis mongDB

mybatis的缓存:

一级缓存(默认支持)

二级缓存 (配置)

1. 什么是查询缓存#

  • MyBatis提供查询缓存,用于减轻数据库压力,提高数据库性能。
  • MyBatis提供一级缓存和二级缓存。
    • 一级缓存是SqlSession级别的缓存,在操作数据库时需要构造SqlSession对象,在对象中有一 个数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间的缓存数据区域 (HashMap)是互相不影响的。
    • 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个mapper的sql语句,多个 SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

image-20240310231336571

2. 一级缓存#

一级缓存是基于PerpetualCache(MyBatis自带)的 HashMap 本地缓存,作用范围为 session 域内。 当 session flush(刷新)或者 close(关闭)之后,该 session 中所有的 cache(缓存)就会被清空。

在参数和 SQL 完全一样的情况下,我们使用同一个 SqlSession 对象调用同一个 mapper 的方法,往往 只执行一次 SQL。因为使用 SqlSession 第一次查询后,MyBatis 会将其放在缓存中,再次查询时,如果 没有刷新,并且缓存没有超时的情况下,SqlSession 会取出当前缓存的数据,而不会再次发送 SQL 到数 据库。

image-20210324113328429

测试:

@Test
public void ca(){
    SqlSessionFactory factory = MyFactoryUtil.getInstance();
    SqlSession sqlSession = factory.openSession();
    EmpDao empDao1 = sqlSession.getMapper(EmpDao.class);
    List<Emp> emps = empDao1.selectByDid(1);
    System.out.println(emps);
    // 过了一段时间
    System.out.println("--------------------------------------------");
    EmpDao empDao2 = sqlSession.getMapper(EmpDao.class);
    List<Emp> emps1 = empDao2.selectByDid(1);
    System.out.println(emps1);
    sqlSession.close();
}

3. 二级缓存#

  • 二级缓存被称为sessionFactory级别的缓存,mybatis默认不支持,需要手动配置
  • 二级缓存的范围更大,多个SqlSession可以共享一个Mapper的二级缓存区域。

image-20210324115031924

启动二级缓存步骤:

1,实体类序列化

public class Emp implements Serializable {
 private long eid;
 private String ename;
 private String esex;
 private long eage;
 private long did;
 // 一对一 查询员工信息的同时把所属的部门信息查到
 private Dept dept;
}

2,再核心配置文件中启动二级缓存

<setting name="cacheEnabled" value="true"/>

3,再映射文件中启动缓存

<!-- 配置当前mapper文件中所有查询语句都放入二级缓存中 -->
<cache/>
<select id="selectByDid" parameterType="long" resultType="emp" >
   select * from emp where did=#{did}
</select>

在statement中设置 useCache="false" 可以禁用当前select语句的二级缓存,即每次查询都会发 出sql去查询。默认情况是true,即该sql使用二级缓存。

4,测试

@Test
    public void ca2(){
        SqlSessionFactory factory = MyFactoryUtil.getInstance();
        SqlSession sqlSession1 = factory.openSession();
        EmpDao empDao1 = sqlSession1.getMapper(EmpDao.class);
        List<Emp> emps = empDao1.selectByDid(1);
        System.out.println(emps);
        sqlSession1.close();
        // 过了一段时间
        System.out.println("--------------------------------------------");
        SqlSession sqlSession2 = factory.openSession();
        EmpDao empDao2 = sqlSession2.getMapper(EmpDao.class);
        List<Emp> emps1 = empDao2.selectByDid(1);
        System.out.println(emps1);
        sqlSession2.close();
    }

十三,Generator代码生成#

虽然MyBatis是一个简单易学的框架,但是配置XML文件也是一件相当繁琐的一个过程,而且会出现很 多不容易定位的错误。当在工作中需要生成大量对象的时候,有太多的重复劳动,简直是生无可恋。

所以,官方开发了MyBatis Generator。它只需要很少量的简单配置,就可以完成大量的表到Java对象 的生成工作,拥有零出错和速度快的优点,让开发人员解放出来更专注于业务逻辑的开发。

1. 生成文件介绍#

MyBatis Generator生成的文件包含三类:

    1. Model实体文件,一个数据库表对应生成一个 Model 实体
    1. Mapper接口文件,数据数操作方法都在此接口中定义
    1. Mapper XML配置文件

2. maven依赖#

<dependencies>
    <!--引入mybatis的依赖-->
    <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>5.1.38</version>
    </dependency>
    <!-- log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--mybatis代码生成器-->
    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.3.7</version>
    </dependency>
</dependencies>

3. 生成配置文件#

我们只需引入log4j.properties即可,无需引入mybatis-config.xml。

注意: 如果我们创建的项目是model则需要再配置中加上model的名称

generator.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC
       "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
       "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration>
   <context id="testTables" targetRuntime="MyBatis3">
       <commentGenerator>
           <!-- 是否去除自动生成的注释 true:是 : false:否 -->
           <property name="suppressAllComments" value="true" />
       </commentGenerator>
       <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
       <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                       connectionURL="jdbc:mysql://localhost:3306/ssm"
                       userId="root"
                       password="123456">
       </jdbcConnection>
       <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC
DECIMAL 和
           NUMERIC 类型解析为java.math.BigDecimal -->
       <javaTypeResolver>
           <property name="forceBigDecimals" value="false" />
       </javaTypeResolver>
       <!-- 生成po模型 -->
       <javaModelGenerator targetPackage="com.newcapec.entity"
targetProject=".\src\main\java">
           <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成
的类放在这个package下 -->
           <property name="enableSubPackages" value="false" />
           <!-- 从数据库返回的值被清理前后的空格 -->
           <property name="trimStrings" value="true" />
       </javaModelGenerator>
       <!-- 生成mapper.xml -->
       <sqlMapGenerator targetPackage="mapper"
targetProject=".\src\main\resources">
           <property name="enableSubPackages" value="false" />
       </sqlMapGenerator>
       <!-- 生成dao接口 -->
       <javaClientGenerator type="XMLMAPPER" targetPackage="com.newcapec.dao2"
targetProject=".\src\main\java">
           <property name="enableSubPackages" value="false" />
       </javaClientGenerator>
       <!--逆向工程不生成example类-->
       <!-- 列出要生成代码的所有表,这里配置的是不生成Example文件 -->
       <table tableName="users" domainObjectName="Users"
              enableCountByExample="false"
              enableUpdateByExample="false"
              enableDeleteByExample="false"
              enableSelectByExample="false"
              selectByExampleQueryId="false">
           <!-- 如果设置为true,生成的model类会直接使用column本身的名字,而不会再使用驼峰
命名方法 -->
           <property name="useActualColumnNames" value="false"/>
       </table>
   </context>
</generatorConfiguration>

Mybatis Generator最完整配置详解:https://blog.csdn.net/qq\_33326449/article/details/10593 0655

4. 生成文件代码#

public class Generator {
   public static void main(String[] args) throws Exception {
       //是否覆盖已有文件
       boolean overwirte = true;
       DefaultShellCallback callback = new DefaultShellCallback(overwirte);
       List<String> warnings = new ArrayList<>();
       //创建配置解析类
       ConfigurationParser configurationParser = new
ConfigurationParser(warnings);
       InputStream in =
Generator.class.getClassLoader().getResourceAsStream("generator.xml");
       Configuration configuration =
configurationParser.parseConfiguration(in);
			 MyBatisGenerator myBatisGenerator = new MyBatisGenerator(configuration,
callback, warnings);
       myBatisGenerator.generate(null);
       System.out.println("代码生成成功...");
   }
}

十四,第三方插件#

1 安装插件#

File -> Settings -> Plugins -> 搜索框输入 Mybatis Tool,点击Install安装。

注意安装完成之后,最好重启IDEA

image-20240310232709783

2 逆向生成mapper类#

通过这个插件不用使用官方的mybatis逆向生成包,写配置文件等等,仅需连接对应数据库就可以实现 逆向生成对应的类、mapper文件等。

第一步<连接数据库>

image-20240310232740585

image-20240310232756233

第二步<配置driver>(首次使用)

点击Driver -> Go to Driver,配置MySQL驱动。

image-20240310232820300

image-20240310232837763

第三步<找到需要逆向生成的表右键选择mybatis-generator>

image-20240310232903590

第四步<配置>

image-20240310232929991

3 跳转功能#

在使用mybatis框架的时候,你还在一个类一个类的点开寻找对应mapper或者dao程序的位置吗?那样 就会显得特别麻烦且浪费时间。而这个Free Mybatis Tool插件提供了跳转的功能。通过点击箭头就可以 跳转到相应的地方。

image-20240310232954355

Mybatis
https://epiphyllum.masttf.fun/post/mybatis
作者
Masttf
发布于
4/29/2025
许可协议
CC BY-NC-SA 4.0