即兴小索奇-MyBatis全套笔记

发布时间:2023年12月20日

一、MyBatis

1、MyBatis简介

1.1、MyBatis历史

MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis(3之前还是iBatis)。代码于2013年11月迁移到Github(.所以现在需要下载的话,需要去Github上去下载,学习了Maven之后,可以从中央仓库或者镜像网站上去下载)。

iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps(把java类中的实体类对象映射为数据库中的一条记录,或者把数据库中的一条记录查询为java中的一条实体类对象)和Data Access Objects(DAO-MyBasis封装的JDBC)。

  • ORM是一个思想x

  • MyBatis就是封装了JDBC,作用就是连接数据库、操作数据库

1.2、MyBatis特性

1) MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架

  • 封装了大量的JDBC代码,之前我们JDBC是字符串拼接和占位符形式拼接到SQL中,Mybasis也有两种方式(#{},¥{})也正好对应我们字符串拼接以及占位符形式拼接到sql中

2) MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集

3) MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java

Objects,普通的Java对象)映射成数据库中的记录

4) MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

  • Hibernate中的SQL不需要我们去写,而MyBasis中的SQL语句是需要我们自己去完成的,JDBC中所有过程都需要我们去写,所以MyBasis相比较而言是一个半自动的ORM框架(对象关系映射-对象指的是java对象,关系指关系型数据库,把我们的java对象和数据库中的记录创建映射关系--把java类中的实体类对象映射为数据库中的一条记录,或者把数据库中的一条记录查询为java中的一条实体类对象)

  • 有两种方式写SQL语句,一种是注解方式,另一种是xml方式,主要讲解xml方式

1.3、MyBatis下载

MyBatis下载地址:GitHub - mybatis/mybatis-3: MyBatis SQL mapper framework for Java

  • 下载后会有文档可以查看

1

2

1.4、和其它持久化层技术对比

  • JDBC

    • SQL 夹杂在Java代码中耦合度高,导致硬编码内伤

    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见

    • 代码冗长,开发效率低

  • Hibernate 和 JPA

    • Hibernate自动生成SQL(Mybasis需要自己写)

    • 操作简便,开发效率高(开发效率比MyBatis高,执行效率MyBasis高)

    • 程序中的长难复杂 SQL 需要绕过框架

    • 内部自动生产的 SQL,不容易做特殊优化

    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。

      • POJO(Plain Ordinary Java Object)是简单的Java对象,实际就是普通JavaBeans

    • 反射操作太多,导致数据库性能下降

  • MyBatis

    • 目前市场上最流行的

    • 轻量级,性能出色

    • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据

    • 开发效率稍逊于HIbernate,但是完全能够接受

    • 连接数据库、操作数据库

2、搭建MyBatis

2.1、开发环境

IDE:idea 2019.2

构建工具:maven 3.5.4

MySQL版本:MySQL 8

MyBatis版本:MyBatis 3.5.7

MySQL不同版本的注意事项

1、驱动类driver-class-name

MySQL 5版本使用jdbc5驱动,驱动类使用:com.mysql.jdbc.Driver

MySQL 8版本使用jdbc8驱动,驱动类使用:com.mysql.cj.jdbc.Driver

)

MySQL 5版本的url:

jdbc:mysql://localhost:3306/ssm

  • 如果连接的数据库没有设置编码,可以再后面加上character

    • 比如jdbc:mysql://localhost:3306/jdbc

    jdbc:mysql:// 是指JDBC连接方式; localhost: 是指你的本机IP地址; 3306 :SQL数据库的端口号; jdbc :就是你要连接的数据库的地址

    ssm就是当前要使用的数据库名称

MySQL 8版本的url:

jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC

  • 默认支持中文编码

  • 不设置时区的话就会报以下错

否则运行测试用例报告如下错误:

java.sql.SQLException: The server time zone value '?D1ú±ê×?ê±??' is unrecognized or

represents more

2.2、创建maven工程

  • 项目结构jdk版本如果是11,则语言级别能选择11及其以下

我们创建一个maven工程加上打包方式

①打包方式:jar

  • 不需要war包

  • 现在测试的只有mybatis功能,而MyBatis就是封装了JDBC,而JDBC是不需要tomcat服务器的,所以创建一个java工程即可

  • ?<packaging>jar</packaging>

②引入依赖

  • 复制粘贴

?<dependencies>
? ? <!-- Mybatis核心 -->
? ? <dependency>
? ? ? ? <groupId>org.mybatis</groupId>
? ? ? ? <artifactId>mybatis</artifactId>
? ? ? ? <version>3.5.7</version>
? ? </dependency>
?<!-- junit测试 -->
? ? <dependency>
? ? ? ? <groupId>junit</groupId>
? ? ? ? <artifactId>junit</artifactId>
? ? ? ? <version>4.12</version>
? ? ? ? <scope>test</scope>
? ? </dependency>
? ? ?
?<!-- MySQL驱动 用的是MySQL8。要是5版本的则version 5-->
? ? <dependency>
? ? ? ? <groupId>mysql</groupId>
? ? ? ? <artifactId>mysql-connector-java</artifactId>
? ? ? ? <version>8.0.16</version>
? ? </dependency>
?</dependencies>

2.3、创建MyBatis的核心配置文件

MyBatis主要有两个核心配置文件

  • 一个就是核心配置文件(配置如何连接数据库)

  • 一个是映射文件(写的是sql语句,如何操作数据库)

习惯上命名为mybatis-config.xml(还有些人命名sqlmapping-config.xml),这个文件名仅仅只是建议,并非强制要求。将来整合Spring

之后,这个配置文件甚至可以省略,所以大家操作时可以直接复制、粘贴。

核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息

核心配置文件存放的位置是maven的src/main/resources目录下

注意:如果创建的只是一个file文件的话,后缀一定要写(比如xml)

创建xml文件后复制粘贴以下内容

  • 入门环节,先测试先跑起来

image-20221222223051147

  • transactionManager是事务管理器

  • dataSource 来帮我们管理数据库连接的,下面的四个参数(drive、url、username、password)连接的数据库不一样,也会有相应变化,所以连接数据库一定会有这四个参数

?<?xml version="1.0" encoding="UTF-8" ?>
?<!DOCTYPE configuration
? ? ? ? ?PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
? ? ? ? ?"http://mybatis.org/dtd/mybatis-3-config.dtd">
?<configuration>
? ? ?
?    <!--设置连接数据库的环境-->
?    <environments default="development">
?        <environment id="development">
?            <transactionManager type="JDBC"/>
?            <dataSource type="POOLED">
?                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
?                <property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
?                <property name="username" value="root"/>
?                <property name="password" value="123456"/>
?            </dataSource>
?        </environment>
?    </environments>
?<!--引入Mybatis映射文件-->
?<mappers>
?    <package name="mappers/UserMapper.xml"/>
?</mappers>
?</configuration>

2.4、创建mapper接口

MyBatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要

提供实现类。

?public interface UserMapper {
? ? ?/**
? ? ?* 添加用户信息
? ? ?*/
? ? ?int insertUser();
?}

2.5、创建MyBatis的映射文件

相关概念:ORMObject Relationship Mapping)对象关系映射。

Mapper接口对应映射中的一个sql语句

  • 对象:Java的实体类对象

  • 关系:关系型数据库

  • 映射:二者之间的对应关系

Java概念数据库概念
属性字段/列
对象记录/行

1、映射文件的命名规则:

表所对应的实体类的类名+Mapper.xml

例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml

因此一个映射文件对应一个实体类,对应一张表的操作一张表对应一个实体类

MyBatis映射文件用于编写SQL,访问以及操作表中的数据

MyBatis映射文件存放的位置是src/main/resources/mappers目录下

2、 MyBatis中可以面向接口操作数据,要保证两个一致:

a>mapper接口的全类名和映射文件的命名空间(namespace)保持一致

b>mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

<?xml version="1.0" encoding="UTF-8" ?>是xml文件的声明

!DOCTYPE mapper指的是当前的一个约束引入的dtd文件

id要和sql语句的id一致当我们去调用mapper中的一个方法,它就会根据当前mapper接口namespace的全类名找到映射文件;然后根据要调用的方法名UserMapper来找到当前的sql语句来获取这个标签中的sql,来执行这个sql

Mapper接口和映射文件一定要保持一致!

  • 这样每一次调用接口中的一个方法就会找到对应的sql()语句(比如MybatisTest.java下面用 mapper.updateUser()就会去找到UserMapper接口中

    int updateUser()方法,它会找到对应映射中sql语句-根据namespace全类名找到映射文件-因为namespace就是UserMapper的全类名)

  • 引入了映射文件,一定不要忘记引入到核心配置文件中(复制粘贴文档代码)(可以发现核心配置文件是和mappers平级的,这样它就能访问mappers文件夹找到下面的文件)

  • Mybatis的映射文件是为了通过读取核心配置文件获取操作数据库的对象,而sql语句在映射文件中,通过加载核心配置文件去找到映射文件中的sql并执行

  • image-20221222231305069

UserMapper

  • namespace要和Mapper接口的全类名保持一致com.atguigu.mybatis.mapper.UserMapper

  • id-sql语句的唯一标识,要和接口中方法名保持一致

  • 通过找到insertUser执行sql语句(User是个具体实现类)

  • 可以实现面向接口执行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.atguigu.mybatis.mapper.UserMapper">
	<!--int insertUser();-->
	<insert id="insertUser">
		insert into t_user values(null,'admin','123456',23,'男','12345@qq.com')
	</insert>
</mapper>

创建了一个t_user表

public interface UserMapper {
    int insertUser();
}

image-20221222221901559

2.6、通过junit测试功能

  • 这些可以进行封装SqlSessionUtil

public class MyBatisTest {
    @Test
    public void testInsert() throws IOException {
        //获取核心配置文件的输入流
        InputStream is= Resources.getResourceAsStream("mybatis-config.xml");
        //因为mybatis不是JDBC,所以不能把JDBC的工程给写一遍,它封装的JDBC
        // 所以需要能找到一个执行sql语句的sql对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionBuilder= sqlSessionFactoryBuilder.build(is);
        //SqlSession sqlSession=sqlSessionBuilder.openSession();
        //自动提交事务的openSession,需要重新复制,不然不是一个方法
        SqlSession sqlSession=sqlSessionBuilder.openSession(true);
        // sqlSession.getMapper(Class<T> type)  T 传进去的是Class对象,返回的是实例对象
        /**
         *  这个方法巧妙的 能够用到接口! 接口不能new ,能帮助我们创建接口的实现类(这个底层用到的是代理模式)
         *  既然是一个实现类,那么一定帮我们重写了UserMapper接口中的方法
         *  原理是通过当前的全类名找到当前的映射文件,在通过当前调用的方法找到映射中的sql语句 并执行,返回结果
         */
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        //调用mapper接口中的方法实现添加用户信息功能
        //update和insert的返回值int是对sql中该sql语句影响的行数,比如你插入一条数据,成功的话sql会提示1行代码收影响,返回的int是1
         //调用mapper接口中的方法,实现添加用户信息的功能
        int result=mapper.insertUser();
        //提供sql以及的唯一标识找到sql并执行,唯一标识是namespace.sqlId
        /*这种方式用的不多:int result = sqlSession.insert("com.atguigu.mybatis.mapper.UserMapper.insertUser");*/
        System.out.println("结果+"+result);
        //注意,如果数据库数据没有显示的话,证明没有提交事务,默认是回滚的,mysql中默认是自动提交的,我们也可以开启自动提交事务
        
        //提交事务
        //sqlSession.commit();
		//关闭SqlSession
        sqlSession.close();

    }
}

文档和视频有略微差异

  • (debug弄半天!!!)报错可以尝试将映射文件package name 改为 mapper resource!!(xml引入)

  • SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的会话)

  • SqlSessionFactory:是“生产”SqlSession的“工厂”。SqlSession是浏览器和sql之间的会话

  • 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的

相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。

封装:

image-20221223162006694

public class SqlSessionUtil {
    public static SqlSession getSqlSession() {
        SqlSession sqlSession=null;
        //往上抛异常的话是抛给调用者了
        try {
            InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
            sqlSession=sqlSessionFactory.openSession(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSession;
    }
}

2.7、加入log4j日志功能

①加入依赖

  • 出现警告:警告是那个文件版本低了,需要的话就alt+shift+enter导入新版本的

<!-- log4j日志 -->
<dependency>
    <groupId>log4j</groupId> 
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

②加入log4j的配置文件

log4j的配置文件名必须为log4j.xml,存放的位置是src/main/resources目录下

如果报红不一定有问题,看看有效果没就行

p11集查看源码debug int result=mapper.insertUser();

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
	<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
		<param name="Encoding" value="UTF-8" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}%m (%F:%L) \n" />
		</layout>
	</appender>
	<logger name="java.sql">
		<level value="debug" />
	</logger>
	<logger name="org.apache.ibatis">
		<level value="info" />
	</logger>
	<root>
		<level value="debug" />
		<appender-ref ref="STDOUT" />
	</root>
</log4j:configuration>

日志的级别

FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)

从左到右打印的内容越来越详细(比如选择debug前面的都会执行)

3、核心配置文件详解

核心配置文件中的标签必须按照固定的顺序:

properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,refl

ectorFactory?,plugins?,environments?,databasemp_idProvider?,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>
	<!--
   MyBatis核心配置文件中,标签的顺序:
   properties?,settings?,typeAliases?,typeHandlers?,
   objectFactory?,objectWrapperFactory?,reflectorFactory?,
   plugins?,environments?,databasemp_idProvider?,mappers?
	-->
	<!--引入properties文件-->
	<properties resource="jdbc.properties" />
	<!--设置类型别名-->
	<typeAliases>
	<!--
		typeAlias:设置某个类型的别名
		属性:
			type:设置需要设置别名的类型
			alias:设置某个类型的别名,若不设置该属性,那么该类型拥有默认的别名,即类名且不区分大小写
	-->
		<!--<typeAlias type="com.atguigu.mybatis.pojo.User"></typeAlias>-->
		<!--以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写-->
		<package name="com.atguigu.mybatis.pojo"/>
	</typeAliases>
	<!--
		environments:配置多个连接数据库的环境
		属性:
			default:设置默认使用的环境的id
	-->
	<environments default="development">
<!--
		environment:配置某个具体的环境
		属性:
			id:表示连接数据库的环境的唯一标识,不能重复
	-->
	<environment id="development">
		<!--
			transactionManager:设置事务管理方式
			属性:
				type="JDBC|MANAGED"
				JDBC:表示当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,事务的提交或回滚需要手动处理
				MANAGED:被管理,例如Spring
		-->
			<transactionManager type="JDBC"/>
			<!--
				dataSource:配置数据源
				属性:
        type:设置数据源的类型
        type="POOLED|UNPOOLED|JNDI"
           POOLED:表示使用数据库连接池缓存数据库连接
           UNPOOLED:表示不使用数据库连接池
           JNDI:表示使用上下文中的数据源
			-->
				<dataSource type="POOLED">
        <!--设置连接数据库的驱动-->
        <property name="driver" value="${jdbc.driver}"/>
           <!--设置连接数据库的连接地址-->
           <property name="url" value="${jdbc.url}"/>
           <!--设置连接数据库的用户名-->
           <property name="username" value="${jdbc.username}"/>
           <!--设置连接数据库的密码-->
           <property name="password" value="${jdbc.password}"/>
				</dataSource>
			</environment>
			<environment id="test">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
       <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <!--老师的源码在这里少些了个?我debug半天-->
       <property name="url"value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
       <property name="username" value="root"/>
       <property name="password" value="123456"/>
			</dataSource>
		</environment>
	</environments>
	<!--引入映射文件-->
	<mappers>
	<!--<mapper resource="mappers/UserMapper.xml"/>-->
	<!--
以包为单位引入映射文件
   要求:
   1、mapper接口所在的包要和映射文件所在的包一致
   2、mapper接口要和映射文件的名字一致
	-->
		<package name="com.atguigu.mybatis.mapper"/>
	</mappers>
</configuration>

4、MyBatis的增删改查

userMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.atguigu.mybatis.mapper.UserMapper">

    <!--
        mapper接口和映射文件要保证两个一致:
        1、mapper接口的全类名和映射文件的namespace一致
        2、mapper接口中的方法的方法名要和映射文件中的sql的id保持一致
    -->

    <!--int insertUser();-->
    <insert id="insertUser">
        insert into t_user values(null,'admin','123456',23,'男','12345@qq.com')
    </insert>

    <!--void updateUser();-->
    <update id="updateUser">
        update t_user set username='root',password='123' where id = 3
    </update>

    <!--void deleteUser();-->
    <delete id="deleteUser">
        delete from t_user where id = 3
    </delete>

    <!--User getUserById();-->
    <!--
        resultType:设置结果类型,即查询的数据要转换为的java类型,这样方便调用查询
        resultMap:自定义映射,处理多对一 或 一对多的映射关系
    -->
    <!--不同包下面是可以创建不同的类的,所以一定要注意写全类名-->
    <select id="getUserById" resultType="com.atguigu.mybatis.pojo.User">
        select *from t_user where id = 1
    </select>

    <!--List<User> getAllUser();-->
    <select id="getAllUser" resultType="com.atguigu.mybatis.pojo.User">
        select * from t_user
    </select>

</mapper>
  • 查询时候把它查询为一个User对象,则刚好对应上我们写的User类(没有实现接口)

  • select别忘了写*

4.1、新增

<!--这是在UserMapper中的 int insertUser();-->
<insert id="insertUser">
	insert into t_user values(null,'admin','123456',23,'男')
</insert>

4.2、删除

<!--int deleteUser();-->
<delete id="deleteUser">
	delete from t_user where id = 7
</delete>

4.3、修改

<!--int updateUser();-->
<update id="updateUser">
	update t_user set username='ybc',password='123' where id = 6
</update>

4.4、查询一个实体类对象

<!--User getUserById();-->
<select id="getUserById" resultType="com.atguigu.mybatis.bean.User">
	select * from t_user where id = 2
</select>

4.5、查询list集合

<!--List<User> getUserList();-->
<select id="getUserList" resultType="com.atguigu.mybatis.bean.User">
	select * from t_user
</select>

注意:

1、查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射

关系

resultType:自动映射,用于属性名和表中字段名一致的情况

resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况

<?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>

    <!--设置连接数据库的环境,MyBatis-config.xml中environments就是配置连接数据库的环境,属性:default设置默认使用的环境id(唯一标识)-->
    <environments default="development">
    <!--id:设置环境的唯一标识,不能重复,environment就是配置具体的连接数据库的环境-->
        <environment id="development">
        <!--
        transactionManager:事物管理器
        属性:
        type:设置事务管理方式
        type="JDBC/MANAGED"
        JDBC:表示使用JDBC中原生的事务管理方式(也就是自动提交我们可以手动开启和关闭,手动提交回滚,比如SqlSession)
        MANAGED:被管理 比如spring 可以教给spring管理
        -->
            <transactionManager type="JDBC"/>
            
        <!--
        这些配置文件了解一下,以后spring整合Mybatis之后可以不配置
        datasource:数据源,
        属性:
        type:设置数据源的类型
        type:"POOLED/UNPOOLED/JNDI"
        POOLED:表示使用数据库连接池
        UNPOOLED:表示不用数据库连接池
        JNDI:表示使用上下文中的数据源
        以前用JDBC我们经常把连接数据库的数据放到properties配置文件中,方便我们的维护。我们的Mybatis当然也可以
      
        -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射文件-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
    <!-- log4j日志 -->

</configuration>

创建的Resource Bundle就是配置文件

我们创建properties配置文件时候不要直接这样写driver=com.mysql.cj.jdbc.Driver

因为后面如果创建多个配置文件,键相等的情况下,如何辨别值呢?

记得加上表示功能的前缀,比如:jdbc.driver=com.mysql.cj.jdbc.Driver防止出现重名问题

jdbc.url…

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

配置完jdbc.peoperties之后还要引入到核心配置文件中,不然没有任何联系

核心配置文件有一个properties标签就是专门为了引入properties文件;

此后就可以在当前文件中使用${key}的方式访问设置value-- ${jdbc.url}

<properties resource="jdbc.properties"/>

mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

<!--
MyBatis核心配置文件中的标签必须要按照指定的顺序配置:
properties?,settings?,typeAliases?,typeHandlers?,
objectFactory?,objectWrapperFactory?,reflectorFactory?,
plugins?,environments?,databasemp_idProvider?,mappers?
-->

<!--引入properties文件,此后就可以在当前文件中使用${key}的方式访问value-->
<properties resource="jdbc.properties" />

<!--
typeAliases:设置类型别名,即为某个具体的类型设置一个别名
在MyBatis的范围中,就可以使用别名表示一个具体的类型
-->
<typeAliases>
<!--
   type:设置需要起别名的类型
   alias:设置某个类型的别名
-->
<!--<typeAlias type="com.atguigu.mybatis.pojo.User" alias="abc"></typeAlias>-->
<!--若不设置alias,当前的类型拥有默认的别名,即类名且不区分大小写-->
<!--<typeAlias type="com.atguigu.mybatis.pojo.User"></typeAlias>-->
<!--通过包设置类型别名(只写typeAlias,不写alias情况下),指定包下所有的类型将全部拥有默认的别名,即类名且不区分大小写(建议写成类名)
以后开发中一张表对应一个实体类,对应一个mapper接口,对应一个映射文件,这样也是比较麻烦的

实体类虽然有很多,但是以后肯定是统一放在同一包下的,那这个时候就可以将实体类所对应的包设置到package这个标签中 	,这个时候这个包下面的所有类将全部拥有默认的别名-即类名-不区分大小写(比如用到User的地方可以改啦)
-->
<package name="com.atguigu.mybatis.pojo"/>
</typeAliases>

<!--
environments:配置连接数据库的环境
属性:
default:设置默认使用的环境的id
-->
<environments default="development">
<!--
   environment:设置一个具体的连接数据库的环境
   属性:
   id:设置环境的唯一标识,不能重复
-->
<environment id="development">
   <!--
       transactionManager:设置事务管理器
       属性:
       type:设置事务管理的方式
       type="JDBC|MANAGED"
       JDBC:表示使用JDBC中原生的事务管理方式
       MANAGED:被管理,例如Spring
   -->
   <transactionManager type="JDBC"/>
   <!--
       dataSource:设置数据源
       属性:
       type:设置数据源的类型
       type="POOLED|UNPOOLED|JNDI"
       POOLED:表示使用数据库连接池
       UNPOOLED:表示不使用数据库连接池
       JNDI:表示使用上下文中的数据源
   -->
   <dataSource type="POOLED">
       <property name="driver" value="${jdbc.driver}"/>
       <property name="url" value="${jdbc.url}"/>
       <property name="username" value="${jdbc.username}"/>
       <property name="password" value="${jdbc.password}"/>
   </dataSource>
</environment>

<environment id="test">
   <transactionManager type="JDBC"/>
   <dataSource type="POOLED">
       <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
       <property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
       <property name="username" value="root"/>
       <property name="password" value="123456"/>
   </dataSource>
</environment>
</environments>

<!--引入mybatis的映射文件-->
<mappers>
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<!--
   以包的方式引入映射文件,但是必须满足两个条件:
   1、mapper接口和映射文件所在的包必须一致
   2、mapper接口的名字和映射文件的名字必须一致
-->
<package name="com.atguigu.mybatis.mapper"/>
</mappers>
</configuration>

创建文件目录new Directory的时候不能直接new com.atguigu…不能点.

目录需要用 / (resources下创建的)

image-20221223213438966

创建之后把UserMapper.xml移动进去了

这样以后不用每创建一个就引入到核心配置文件中了

image-20221223214826893

可能有疑问:这两个虽然是同一个包,但是不在同一个目录下啊(java是放java文件的,resource是放配置文件的)它们加载之后是放在同一个目录下的,在target/classes编译之后放的目录,可以发现它们是在同一个目录下的

java和resource只是为了区分主程序和配置文件,来区分的一个目录,但是加载时候会加载一个目录下

别名拓展

使用简单类名的方式需要在MyBatis的配置文件中配置别名。在MyBatis中,可以使用<typeAliases>元素定义Java类的别名,这样就可以在resultType属性中使用简单类名来指定类了。

例如,可以在MyBatis的配置文件中定义一个User类的别名:

<typeAliases>
  <typeAlias alias="User" type="com.example.User"/>
</typeAliases>

这个配置定义了一个名为User的别名,它对应的Java类是com.example.User。这样,就可以在MyBatis的SQL映射文件中使用User来指定该类了:

<select id="getUserById" resultType="User">
  SELECT id, username, password FROM user WHERE id = #{id}
</select>

IDEA创建模板功能

Editor下面的File and Code Templates,file添加模板,可以new出来

注意:

  • 如果用包的方式引入映射文件,映射文件所对应的包和mapper接口所对应的包必须要保持一致(不然怎么区分哪一个配置文件对应哪一个mapper接口)

尚硅谷19集创建总结

项目文件目录

image-20221224220425000

5、MyBatis获取参数值的两种方式

我们开发B/S 通过视图收集用户输入的数据,然后把这些数据提交到服务器中,在服务器中收到之后传输到Server->DAO在DAO的实现类中,要把这些数据拼接到sql语句中,执行sql语句

  • 以后我们很少见到写死的sql,除了 select *from 查询

MyBatis获取参数值的两种方式:${}#{}

${}的本质就是字符串拼接,#{}的本质就是占位符赋值

  • 我们常用占位符方式,既可以避免sql注入,又简单,单引号不需要自己加

${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引

号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,

可以自动添加单引号

  • tips:如果字段本身是int类型,如果查询条件中加了引号,比如select * from user where id=‘4’,这时候可以查出id=4的用户信息,但是使用select * from user where id='4abc’同样可以查出这条信息。网上说是mysql进行了隐式处理,后面的字符变成了0和前面的数字加起来。

5.1、单个字面量类型的参数

若mapper接口中的方法参数为单个的字面量类型

此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号,会造成sql注入,#{}可以避免sql注入

一个参数情况:

注意${}需要手动加单引号'${}' ,这个括号里面虽然可以随便写(和#{}一样-一个参数才可以这样),但是不建议使用数字,因为数字有运算功能,建议传输过了的参数名是什么就写什么(没有使用@Param时)

不加的话,报错,不认识admin这个字段

image-20221224223410462

6、MyBatis的各种查询功能

5.2、多个字面量类型的参数

若mapper接口中的方法参数为多个时

此时MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1...为键,以参数为值;以

param1,param2...为键,以参数为值;因此只需要通过${}和#{}访问map集合的键就可以获取相

对应的值,注意${}需要手动加单引号

5.3、map集合类型的参数

若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在

map中

只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

5.4、实体类类型的参数

若mapper接口中的方法参数为实体类对象时

此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值注意${}需要手动加单引号

get和set方法,属性名详解
  • 属性名是什么,比如成员变量User里面的id,username准确来说不是属性名,它里面的getId()、setId()去掉get和set ,小写id即是属性名,所以在没有这个成员变量的情况下,只有get、set方法不影响我们去访问这个属性

  • 一个是属性,一个是成员变量。属性对应数据库对象的属性,成员变量是程序的变量。到springboot的时候还要写代码把属性和成员变量对应起来

  • 因为mybatis是通过get方法来获取属性值的

  • 意思是:如果你的getId方法,return的是你username的值,那我不管,我就认为你的这个username叫id属性;

  • 弹幕:属性名和成员变量有关系,getter拿不到,就去找成员变量--注释掉setter和getter也可以

5.5、使用@Param标识参数

可以通过@Param注解标识mapper接口中的方法参数

此时,会将这些参数放在map集合中,以@Param注解的value属性值为键,以参数为值;以

param1,param2...为键,以参数为值;只需要通过${}和#{}访问map集合的键就可以获取相对应

的值,

@Param(value="username")这是一个value属性,只为value属性赋值的话,value可以省略不写,即@Param("username"),@Param("password")

MyBatis仍然会把它放到map集合里面,它会默认把注解中的value属性作为键

6.1、查询一个实体类对象

Mybatis获取参数值的两种方式:#{}和${}

  • 若mapper接口方法的参数为单个字面量类型,此时可以通过#{}和${}以任意的内容获取参数值(这是Mybatis3.5版本,在3.4版本差别很大,${}不能以任意的内容填充{},只有两种方法,了解即可),一定要注意${}要加单引号问题

  • #{}的本质是占位符赋值,${}的本质是字符串拼接

  • #{}可以自动加单引号,${}手动加单引号'${}'

  • #{}并不会改变原本的SQL规则,占位符?处会被完整替换

/**
* 根据用户id查询用户信息
* @param id
* @return
*/
User getUserById(@Param("id") int id);
<!--User getUserById(@Param("id") int id);设置了别名所以直接类名即可 #{id}这里面的名字可以随便写,底层代码并不知道它是谁-不写@param的情况下,建议传过来的是id 写的 就是 id-->
<select id="getUserById" resultType="User">
	select * from t_user where id = #{id}
</select>

6.2、查询一个list集合

resultType是将查询结果映射到一个具体的Java对象类型

resultMap则是更为灵活和强大的一种映射方式,可以将查询结果映射到任意的Java对象结构中。resultMap可以定义多个映射规则,可以包含复杂的映射逻辑和转换操作。

/**
* 查询所有用户信息
* @return
*/
List<User> getUserList();
<!--List<User> getUserList();-->
<select id="getUserList" resultType="User">
	select * from t_user
</select>

当查询的数据为多条时,不能使用实体类作为返回值,否则会抛出异常

TooManyResultgenderception;但是若查询的数据只有一条,可以使用实体类或集合作为返回值

6.3、查询单个数据

  • resultType后面只能写两种,一种是类型别名(别名是不区分大小写的),另外一种就是具体的某一个类型

/**
* 查询用户的总记录数count()
* 如果为count(age),而age中有一个为null,则当前这个记录不计入总数据
* @return
* 在MyBatis中,对于Java中常用的类型都设置了类型别名
* 例如:  常见这几种:
* java.lang.Integer-->int|integer
* 例如: int-->_int|_integer
* 例如: Map-->map,List-->list
*/
int getCount();
<!--id里面写任意数字查询出来都一样的,唯一不一样的是里面写字段,如果为count(age),而age中有一个为null,则当前这个记录不计入总数据-->
<!-- 查询出来的是Integer类型(SelectMapper接口-Integer getCount();),所以我们resultType则写java.lang.Integer,在Mybatis中为_integer-->
<!--int getCount();-->
<select id="getCount" resultType="_integer">
	select count(id) from t_user
</select>

Mybatis提供的常用别名(8中基本数据类型都是前面加 _)

image-20221225164135299

6.4、查询一条数据为map集合

我们查询出来的结果可能对应不上固定的类型,当我们查询出来的结果没有对应的实体类的时候(情况很多,很常用),我们要查询一条数据为map集合(不固定,可以通过属性名获取属性值)

/**
* 根据用户id查询用户信息为map集合
* @param id
* @return
*/
Map<String, Object> getUserToMap(@Param("id") int id);
<!--Map<String, Object> getUserToMap(@Param("id") int id);-->
<!--结果: {password=123456, gender=男 , id=1, age=23, username=admin} 如果有的字段值为null是不会被查询出来的-->
<select id="getUserToMap" resultType="map">
	select * from t_user where id = #{id}
</select>

6.5、查询多条数据为map集合

①方式一(用的多)

/**
* 查询所有用户信息为map集合,Map<String,Object>,查询到的数据以键值对形式放到map中
* @return
* 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,此
* 时可以将这些map放在一个list集合中获取	
*/
List<Map<String, Object>> getAllUserToMap();
<!--Map<String, Object> getAllUserToMap();-->
<select id="getAllUserToMap" resultType="map">
	select * from t_user
</select>
   @Test
    public void testGetAllUserToMap(){
        SqlSession sqlSession=SqlSessionUtil.getSqlSession();
        SelectMapper mapper=sqlSession.getMapper(SelectMapper.class);
        List<Map<String, Object>> list =mapper.getAllUserToMap();
        System.out.println(list);
    }

②方式二

  • Map集合也是可以存放多条数据,但是Map和List不一样,能把每一条数据转换为Map集合放到List中,但是不是把每一条数据转换为Map集合放到Map中

    因为Map是键值对

    查询出来的数据能作为值,但是谁作为键呢?

    所以引入注解@MapKey() 把我们当前所查询的数据转换为Map集合,然后放到一个大的Map集合中,然后通过这个注解可以获得Map集合的键

    //    比如我们把查询出来的id来作为map的键,然后它的值就是我们当前数据所转换为的Map集合
    //  dao层 SelectMapper.java接口
        @MapKey("id")
        Map<String,Object> getAllUserToMap();
    	<!--   SelectMapper.xml-->
       <select id="getAllUserToMap" resultType="map">
            select *from t_user
        </select>
       @Test
        public void testGetAllUserToMap(){
            SqlSession sqlSession=SqlSessionUtil.getSqlSession();
            SelectMapper mapper=sqlSession.getMapper(SelectMapper.class);
         /*   List<Map<String, Object>> list =mapper.getAllUserToMap();
            System.out.println(list);*/
            Map<String ,Object> map=mapper.getAllUserToMap();
            System.out.println(map);
        }

    输出结果

    1、2、3就是对应的键

    {1={password=123456, gender=男, id=1, age=23, email=12345@qq.com, username=admin},
     2={password=123456, gender=男, id=2, age=23, email=12345@qq.com, username=admin1}, 
     3={password=123456, gender=男, id=3, age=23, email=12345@qq.com, username=admin2}

注意:

补充:

不用@Param时

  • 如果参数有两个的时候,Mybatis会自动把这两个参数放到map集合中(我们也可以自定义map放置)

  • Available parameters are [arg1, arg0, param1, param2],#{},#{}中可以写以上两种值(当然,也可以混用,因为都键值对的形式存到map中-建议用一种即可比如arg0,arg1)

  • image-20221224231014176

报错分析
  • 填写username,password报的错。

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause://解析配置文件时候的错误
org.apache.ibatis.binding.BindingException://绑定参数时候的异常:
Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2] //告诉我们可利用的参数
### Cause: org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2]

可以更改为arg0,arg1,或者是param1,param2

image-20221224230458540

报错2:TooManyResultgenderception

原因分析:

因为User 对应一个用户,查询的时候写了 select *from t_user 会出来多个数据,所以查询结果集报错

我们用mapper时候,其实在调用底层SqlSession的方法,点它会发现,有select,insert,delete等方法,这是它调用的是SqlSession中的SqlSession.SelectOne()

所以如果为多条数据,要用一个List集合,

image-20221225151037095

易错点总结:

${}和#{},通过访问实体类对象中的属性名获取属性值注意${}需要手动加单引号

baidu:在Java中,实体类就是一个拥有Set和Get方法的类

记的时候只记两种即可,一种是实体类型的参数,直接通过属性名获取属性值,其它全部情况加上@Param,@Param注解的方式是通过value属性值来获取参数值的,如果不加,可能不知道是如何获取参数值的,加了就一定是通过param注解来获取value属性值的

  • 相当于在UserMapper接口中指定了@Param 如:

    User checkLoginByParam(@Param("username") String uname,@Param("password") String pwd);

    那么在UserMapper映射文件中#{username} #{password} 属性值就是

    <select id="checkLoginByParam" resultType="User">
        select *from t_user where username=#{username} and password=#{password}
    </select>
  • username=#{username} and password=#{password}

单个参数,多个参数什么的都可以用注解,不用注解时候,#{}里面填写什么都可以,用了注解,@Param("username") String username,映射文件中直接定了#{}里面是username

?

? ? ?select *from t_user where username=#{username}?

?

CSDN @Param注解的用法

1、概述

首先明确这个注解是为SQL语句中参数赋值而服务的。

@Param的作用就是给参数命名,比如在mapper里面某方法A(int id),当添加注解后A(@Param("userId") int id),也就是说外部想要取出传入的id,只需要取它的参数名userId就可以了。将参数值传如SQL语句中,通过#{userId}进行取值给SQL的参数赋值。

2、实例:

实例一:@Param注解基本类型的参数

mapper中的方法:

public User selectUser(@Param("userName") String name,@Param("password") String pwd);

映射到xml中的<select>标签

<select id="selectUser" resultMap="User">  
select * from user  where user_name = #{userName} and user_password=#{password}  
</select>

其中where user_name = #{userName} and user_password = #{password}中的userName和password都是从注解@Param()里面取出来的,取出来的值就是方法中形式参数 String name 和 String pwd的值。

实例二:@Param注解JavaBean对象

SQL语句通过@Param注解中的别名把对象中的属性取出来然后赋值

mapper中的方法:

public List<User> getAllUser(@Param("user") User u);

3、注意点

当使用了@Param注解来声明参数的时候,SQL语句取值使用#{},${}取值都可以。

当不使用@Param注解声明参数的时候,必须使用的是#{}来取参数。使用${}方式取值会报错。

不使用@Param注解时,参数只能有一个,并且是Javabean。在SQL语句里可以引用JavaBean的属性,而且只能引用JavaBean的属性。

@Select("SELECT * from Table where id = #{id}")
Enchashment selectUserById(User user);

对应的文件

UserMapper.java接口

package com.atguigu.mybatis.mapper;

import com.atguigu.mybatis.pojo.User;
import org.apache.ibatis.annotations.Param;

import java.util.Map;

/**
 * Date:2022/6/28
 * Author:ybc
 * Description:
 * MyBatis获取参数值的两种方式:#{}和${}
 * #{}的本质是占位符赋值,${}的本质是字符串拼接
 * 1、若mapper接口方法的参数为单个的字面量类型
 * 此时可以通过#{}和${}以任意的内容获取参数值,一定要注意${}的单引号问题
 * 2、若mapper接口方法的参数为多个的字面量类型
 * 此时MyBatis会将参数放在map集合中,以两种方式存储数据
 * a>以arg0,arg1...为键,以参数为值
 * b>以param1,param2...为键,以参数为值
 * 因此,只需要通过#{}和${}访问map集合的键,就可以获取相对应的值,一定要注意${}的单引号问题
 * 3、若mapper接口方法的参数为map集合类型的参数
 * 只需要通过#{}和${}访问map集合的键,就可以获取相对应的值,一定要注意${}的单引号问题
 * 4、若mapper接口方法的参数为实体类类型的参数
 * 只需要通过#{}和${}访问实体类中的属性名,就可以获取相对应的属性值,一定要注意${}的单引号问题
 * 5、可以在mapper接口方法的参数上设置@Param注解
 * 此时MyBatis会将这些参数放在map中,以两种方式进行存储
 * a>以@Param注解的value属性值为键,以参数为值
 * b>以param1,param2...为键,以参数为值
 * 只需要通过#{}和${}访问map集合的键,就可以获取相对应的值,一定要注意${}的单引号问题
 */
public interface UserMapper {

    /**
     * 根据用户名查询用户信息
     * @param username
     * @return
     */
    User getUserByUsername(String username);

    /**
     * 验证登录
     * @param username
     * @param password
     * @return
     */
    User checkLogin(String username, String password);

    /**
     * 验证登录(以map集合作为参数)
     * @param map
     * @return
     */
    User checkLoginByMap(Map<String, Object> map);

    /**
     * 添加用户信息
     * @param user
     */
    void insertUser(User user);

    /**
     * 验证登录(使用@Param)
     * @param username
     * @param password
     * @return
     */
    User checkLoginByParam(@Param("username") String username, @Param("password") String password);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.atguigu.mybatis.mapper.UserMapper">
	<!--id要和方法名一致-->
    <!--User getUserByUsername(String username);-->
    <select id="getUserByUsername" resultType="User">
        <!--select * from t_user where username = #{username}-->
        select * from t_user where username = '${username}'
    </select>

    <!--User checkLogin(String username, String password);-->
    <select id="checkLogin" resultType="User">
        <!--select * from t_user where username = #{param1} and password = #{param2}-->
        select * from t_user where username = '${param1}' and password = '${param2}'
    </select>

    <!--User checkLoginByMap(Map<String, Object> map);-->
    <select id="checkLoginByMap" resultType="User">
        select * from t_user where username = #{username} and password = #{password}
    </select>
	<!--null让它id自动增长,它也是属性名和属性值,所以也是map集合-->
    <!--void insertUser(User user);-这里传过来的就是实体类对象->
    <insert id="insertUser">
        insert into t_user values(null,#{username},#{password},#{age},#{gender},#{email})
    </insert>

    <!--User checkLoginByParam(@Param("username") String username, @Param("password") String pwd);-->
    <select id="checkLoginByParam" resultType="User">
        select * from t_user where username = #{username} and password = #{password}
    </select>

</mapper>

ParameterTest.java

package com.atguigu.mybatis.test;

import com.atguigu.mybatis.mapper.UserMapper;
import com.atguigu.mybatis.pojo.User;
import com.atguigu.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 即兴小索奇
 * @version 1.0
 * @date 22/12/24  20:58
 * @description
 */
public class ParameterTest {
    @Test
    public void testGetUserByusername() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserByUsername("admin1");
        System.out.println(user);
    }

    @Test
    public void testCheckLogin() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.checkLogin("admin1", "123456");
        System.out.println(user);
    }

    @Test
    public void testCheckLoginByMap() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("username", "admin1");
        map.put("password", "123456");
        User user = mapper.checkLoginByMap(map);
        System.out.println(user);
    }

    @Test
    public void testInsert() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(null, "root", "123456", 33, "女", "12345.qq.com");
        mapper.insertUser(user);
    }
    @Test
    public void testCheckLoginByParam(){
        SqlSession sqlSession=SqlSessionUtil.getSqlSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        userMapper.checkLoginByParam("admin1","123456");
    }
}

SelectMapper.java

package com.atguigu.mybatis.mapper;

import com.atguigu.mybatis.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;

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

/**
 * Date:2022/6/28
 * Author:ybc
 * Description:
 */
public interface SelectMapper {

    /**
     * 若sql语句查询的结果为多条时,一定不能以实体类类型作为方法的返回值
     * 否则会抛出异常TooManyResultgenderception
     * 若sql语句查询的结果为1条时,此时可以使用实体类类型或list集合类型作为方法的返回值
     */

    /**
     * 根据id查询用户信息
     * @param id
     * @return
     */
    User getUserById(@Param("id") Integer id);

    /**
     * 查询所有的用户信息
     * @return
     */
    List<User> getAllUser();

    /**
     * 查询用户的总数量
     * @return
     */
    Integer getCount();

    /**
     * 根据id查询用户信息为map集合
     * @param id
     * @return
     */
    Map<String, Object> getUserByIdToMap(@Param("id") Integer id);

    /**
     * 查询所有的用户信息为map集合
     * 若查询的数据有多条时,并且要将每条数据转换为map集合
     * 此时有两种解决方案:
     * 1、将mapper接口方法的返回值设置为泛型是map的list集合
     * List<Map<String, Object>> getAllUserToMap();
     * 结果:{password=123456, gender=男, id=1, age=23, email=12345@qq.com, username=admin},{password=123456, gender=男, id=1, age=23, email=12345@qq.com, username=admin}
     * 2、可以将每条数据转换的map集合放在一个大的map中,但是必须要通过@MapKey注解
     * 将查询的某个字段的值作为大的map的键
     * @MapKey("id")
     * Map<String, Object> getAllUserToMap();
     * 结果:
     * {
     *  1={password=123456, gender=男, id=1, age=23, email=12345@qq.com, username=admin},
     *  2={password=123, gender=男, id=2, age=23, email=12345@qq.com, username=zhangsan},
     *  3={password=123456, gender=女, id=3, age=33, email=123@qq.com, username=root},
     *  4={password=123, id=4, username=lisi}
     *  }
     */
    //List<Map<String, Object>> getAllUserToMap();
    @MapKey("id")
    Map<String, Object> getAllUserToMap();
}

SelectMapperTest.java

package com.atguigu.mybatis.test;

import com.atguigu.mybatis.mapper.SelectMapper;
import com.atguigu.mybatis.pojo.User;
import com.atguigu.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

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

/**
 * Date:2022/6/28
 * Author:ybc
 * Description:
 */
public class SelectMapperTest {

    @Test
    public void testGetUserById(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        User user = mapper.getUserById(1);
        System.out.println(user);
    }

    @Test
    public void testGetAllUser(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        List<User> list = mapper.getAllUser();
        list.forEach(System.out::println);
    }

    @Test
    public void testGetCount(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        Integer count = mapper.getCount();
        System.out.println(count);
    }

    @Test
    public void testGetUserByIdToMap(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        Map<String, Object> map = mapper.getUserByIdToMap(4);
        //{password=123456, gender=男, id=1, age=23, email=12345@qq.com, username=admin}
        System.out.println(map);
    }

    @Test
    public void testGetAllUserToMap(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        /*List<Map<String, Object>> list = mapper.getAllUserToMap();
        System.out.println(list);*/
        /**
         * {
         * 1={password=123456, gender=男, id=1, age=23, email=12345@qq.com, username=admin},
         * 2={password=123, gender=男, id=2, age=23, email=12345@qq.com, username=zhangsan},
         * 3={password=123456, gender=女, id=3, age=33, email=123@qq.com, username=root},
         * 4={password=123, id=4, username=lisi}
         * }
         */
        Map<String, Object> map = mapper.getAllUserToMap();
        System.out.println(map);
    }


}

SelectMapper.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.atguigu.mybatis.mapper.SelectMapper">

<!--    User getUserById(@Param("id") Integer id);-->
    <select id="getUserById" resultType="User">
        select * from t_user where id = #{id}
    </select>

<!--    List<User> getAllUser();-->
    <select id="getAllUser" resultType="User">
        select * from t_user
    </select>

<!--    Integer getCount();-->
    <!--
        在MyBatis中,对于Java中常用的类型都设置了类型别名
        例如: java.lang.Integer -> int|integer
        例如: int -> _int|_integer
        例如: Map -> map,
        例如: List -> list
    -->
<!--    <select id="getCount" resultType="java.lang.Integer">-->
    <select id="getCount" resultType="Integer">
        select count(*) from t_user
    </select>

    <!--Map<String, Object> getUserToMap(@Param("id") int id);-->
    <!--结果: {password=123456, gender=男 , id=1, age=23, username=admin}-->
    <select id="getUserToMap" resultType="map">
        select * from t_user where id = #{id}
    </select>

    <!--Map<String, Object> getAllUserToMap();-->
    <select id="getAllUserToMap" resultType="map">
        select * from t_user
    </select>

</mapper>

7、特殊SQL的执行

7.1、模糊查询

  • 如果查询不确定查询数据具体有几条的话,那最好用List集合来获取

  • '%#{mohu}%' 中的#{mohu}会被解析成一个?当成字符串(因为#{}对应的是占位符赋值)

/**
* 测试模糊查询
* @param mohu
* @return
*/
public interface SpecialSQLMapper {
    List<User> getUserByLike(@Param("mohu") String mohu);
}
<!--List<User> testMohu(@Param("mohu") String mohu);(user为集合list中的实体类),注意不是写成list-->
<select id="testMohu" resultType="User">
    <!--${mohu}是字符串拼接,它会把其拼接到sql语句中-->
    <!--select * from t_user where username like '%${mohu}%'  -->
    <!--select * from t_user where username like concat('%',#{mohu},'%')-->
    <!--下面这个固定语法,用的也是最多的,会被底层自动解析为'%',#{mohu},'%'-->
	select * from t_user where username like "%"#{mohu}"%"
</select>
  @Test
    public void testGetUserByLike(){
        SqlSession sqlSession= SqlSessionUtil.getSqlSession();
        SpecialSQLMapper mapper=sqlSession.getMapper(SpecialSQLMapper.class);
        List<User> list=mapper.getUserByLike("a");
        list.forEach(System.out::println);
    }

7.2、批量删除

  • 数据库中删除操作:delete from t_user where id in (9,10)

  • delete from t_user where id in(9,10)

  • delete from t_user where id=9 or id=10

在MySQL5.5中中delete from t_user where id in ('9,10')这样的永远只会删除第一个,而不报错,在Mybatis中会报错和MySQL

/**
* 批量删除 现在只能用${}标签删除
* @param ids
* @return
*/
int deleteMore(@Param("ids") String ids);
<!--int deleteMore(@Param("ids") String ids);-->
<delete id="deleteMore">
	delete from t_user where id in (${ids})
</delete>

  @Test
    public void testDeleteMoremp_ids(){
        SqlSession sqlSession= SqlSessionUtil.getSqlSession();
        SpecialSQLMapper mapper=sqlSession.getMapper(SpecialSQLMapper.class);
        mapper.deleteMoreUser("4,5");
    }

拓展 in ()括号中跟的是具体的值,如in(1,3)对应的不是1-3范围,是1和3,从1开始就是第一条记录

7.3、动态设置表名

/**
* 也是只能用${}
* 动态设置表名,查询所有的用户信息(如果是普通用户查询为用* 户表,如果为vip查询为vip表)
* @param tableName
* @return
*/
List<User> getAllUser(@Param("tableName") String tableName);
<!--List<User> getAllUser(@Param("tableName") String tableName);-->
<select id="getAllUser" resultType="User">
	select * from ${tableName}
</select>
   @Test
    public void testGetUserList(){
        SqlSession sqlSession= SqlSessionUtil.getSqlSession();
        SpecialSQLMapper mapper=sqlSession.getMapper(SpecialSQLMapper.class);
        List<User> list=mapper.getUserList("t_user");
        list.forEach(System.out::println);
    }

7.4、添加功能获取自增的主键

对应34集

场景模拟(添加班级的同时为班级分配学生):

t_clazz(clazz_id,clazz_name)

t_student(student_id,student_name,clazz_id)

1、添加班级信息

2、获取新添加的班级的id

3、为班级分配学生,即将某学的班级id修改为新添加的班级的id

  • 可以用JDBC自带的功能,当我们实现添加功能之后,可以直接获取它自增的id(获取自增的主键,继续实现其它功能,使用很多!

  • 这个表可能很多人在访问,其他人如果添加的话,id会增加,我们如果直接获取最大ID的话可能获取不准确

/**
* 添加用户信息
* @param user
* @return
* useGeneratedKeys:设置使用自增的主键
* keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user对象的某个属性中
*/
int insertUser(User user);
  <!--不仅要添加,这里还要获取自增的主键
	useGenerateKeys: 表示是否使用自增的主键,
    keyProperty(主键属性):把添加数据的自增的主键为实体类类型的参数属性赋值,增删改只能void、int类型,不能作为返回值返回
    我们只能操作传输过来的User类型的参数,把我们查询出来的自增的主键 放在 实体类的属性中就能实现
    查的是id,获取的就是自增的主键,所以这里写id
    -->
    <!--void insertUser(User user);-->
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into t_user values(null,#{username},#{password},#{age},#{gender},#{email})
    </insert>
void insertUser(User user);
   @Test
    public void testInsertUser(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        SpecialSQLMapper mapper = sqlSession.getMapper(SpecialSQLMapper.class);
        User user = new User(null,"xiaoming","123456",23,"男","123@qq.com");
        System.out.println(user); //插入前:User{id=null, username='xiaoming', password='123456', age=23, gender='男', email='123@qq.com'}
        mapper.insertUser(user);
        //能获取到id,如果改为其它属性,比如age,email则不可获取了
        System.out.println(user); //插入后:User{id=20, username='xiaoming', password='123456', age=23, gender='男', email='123@qq.com'}
    }

  • 增删改返回值只能是int类型,只能表示受影响的行数

JDBC中的功能:(Mybatis封装的JDBC,JDBC有Mybatis才有啊)

可以使用数字,但是非常建议使用常量(清晰明了 )

image-20221226125950366

之前的insert不能输出id的:

  @Test
    public void testInsert() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(null, "root", "123456", 33, "女", "12345.qq.com");
        System.out.println(user);
        mapper.insertUser(user);
        System.out.println(user);
    }
//控制台输出User{id=null, username='root', password='123456', age=33, gender='女', email='12345.qq.com'}
//User{id=null, username='root', password='123456', age=33, gender='女', email='12345.qq.com'}
//前后一致,不能查到id
<!--    void insertUser(User user);-->
    <select id="insertUser" resultType="User">
        insert into t_user values(null,#{username},#{password},#{age},#{gender},#{email})
    </select>
//UserMapper.java
void insertUser(User user);

8、自定义映射resultMap

8.1、resultMap处理字段和属性的映射关系

若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射

一对多or多对一

多对一的时候,我们要把字段设置在多的地方,比如emp员工对应dept部门,设置在员工那里

emp:

image-20221226142858720

dept:

image-20221226142827738

<!--
    resultMap:设置自定义映射
    属性:
    id:表示自定义映射的唯一标识
    type:查询的数据要映射的实体类的类型
    子标签:
    id:设置主键的映射关系
    result:设置普通字段的映射关系
    association:设置多对一的映射关系
    collection:设置一对多的映射关系 collection不需要设置javaType,因为已经是一个集合了,设置集合中的类型ofType
    属性:
    property:设置映射关系中实体类中的属性名
    column:设置映射关系中表中的字段名
-->
<resultMap id="userMap" type="User">
    <id property="id" column="id"></id>
    <result property="userName" column="user_name"></result>
    <result property="password" column="password"></result>
    <result property="age" column="age"></result>
    <result property="gender" column="gender"></result>
</resultMap>
<!--List<User> testMohu(@Param("mohu") String mohu);-->
<select id="testMohu" resultMap="userMap">
	<!--select * from t_user where username like '%${mohu}%'-->
select id,user_name,password,age,gender from t_user where user_name likeconcat('%',#{mohu},'%')
</select>

字段名和属性名不一致会查询不出结果:

t_emp:

image-20221226152111043

Emp类:

private Integer empId;
 private String  empName;
 private Integer age;
 private String gender;

<!--Emp getEmpByEmpId(@Param("empId") Integer empId);  根据数据库中名去实体类中找的时候肯定找不到,名字不同-->
 <select id="getEmpByEmpId" resultType="Emp">
     select from t_emp where emp_id=#{empId}
 </select>
public interface EmpMapper {
 Emp getEmpByEmpId(@Param("empId") Integer empId);
}
@Test
 public void testGetEmpByEmpId(){
     SqlSession sqlSession= SqlSessionUtil.getSqlSession();
     EmpMapper mapper=sqlSession.getMapper(EmpMapper.class);
     Emp emp= mapper.getEmpByEmpId(1);
     System.out.println(emp);
 }

image-20221226151843179

字段名(如:emp_id)和实体类(如:empId)中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类中的属性

名符合Java的规则(使用驼峰)

此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系

  • a>可以通过为字段起别名的方式,保证和实体类中的属性名保持一致

  • b>可以在MyBatis的核心配置文件中的setting设置一个全局配置信息mapUnderscoreToCamelCase,可

以在查询表中数据时,自动将_类型的字段名转换为驼峰(和类中的属性命名方式一致)

例如:字段名user_name,设置了mapUnderscoreToCamelCase,此时字段名就会转换为

userName(这些都可以在Mybatis中找到,注意:此时用的还是resultType="Emp",而不是resultMap自定义映射

 <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

<!--因为要用处理的实体类是Emp,所以type这里写Emp(这里不是自定义映射resultMap),如果想要查询Dept里面的类型,则查询不了,需要映射Dept里面的属性-->
<select id="getEmpByEmpId" resultType="Emp">
        <!--select emp_id empId,emp_Name empName,age,gender from t_emp where emp_id=#{empId}-->
        select *from t_emp where emp_id=#{empId}
    </select>

image-20221226154544965

8.2、多对一映射处理

场景模拟:

查询员工信息以及员工所对应的部门信息

Emp中与Dept有关的就只有Dept,所以映射的时候要与Dept这个属性中的属性映射(比如dept.id) 在映射关系中,要用resultMap映射,写 
<result column="dept_id" property="dept.deptId"></result>

Emp类
private Integer empId;
 private String  empName;
 private Integer age;
 private String gender;
 private Dept dept;

8.2.1、级联方式处理多对一映射关系(一)

第一种方式

未处理:

image-20221227113458088

Emp类:

image-20221227114009418

Dept类:

image-20221227114255060

Emp表:

image-20221227114122840

Dept表:

image-20221227114205687

<!--这里主要是Emp查询,如果想要查询Dept里面的类型,则查询不了,需要映射Dept里面的属性。和Emp相关则type里面写Emp(Emp里面才有Emp emp属性)-->
<resultMap id="empAndDeptResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="empt_name" property="emptName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
    <!--Emp中有Dept dept属性  原老师笔记写的都是empid,empname…,这里我更改了-笔记视频不一致-->
    <result column="dept_id" property="dept.deptId"></result>
    <result column="dept_name" property="dept.deptName"></result>
</resultMap>
   <!--Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);-->
 <select id="getEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">
        select
        t_emp.*,t_dept.*
        from t_emp
        left join t_dept
        on t_emp.dept_id = t_dept.dept_id
        where t_emp.emp_id = #{empId}
    </select>

8.2.2、使用association处理多对一映射关系(二)

  <resultMap id="empAndDeptResultMap" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <!--
            association:处理多对一的映射关系(处理实体类类型的属性)
            property:设置需要处理映射关系的属性的属性名
            javaType:设置要处理的属性的类型
        -->
        <association property="dept" javaType="Dept">
            <id column="dept_id" property="deptId"></id>
            <result column="dept_name" property="deptName"></result>
        </association>
    </resultMap>
<!--Emp getEmpAndDeptByemp_id(@Param("emp_id") int emp_id); resultMap="" 这里写resultMap的id-->
<select id="getEmpAndDeptByemp_id" resultMap="empDeptMap">
	select emp.*,dept.* from t_emp emp left join t_dept dept on emp.dept_id =dept.dept_id where emp.emp_id = #{emp_id}
</select>

8.2.3、分步查询处理多对一(三)

对应41集

一个表对应一个实体类对应一个Mapper接口对应一个映射文件,我们写不同类型的映射查询语句时一定不要写在同一个里面(再创建相应的接口映射文件)

①查询员工信息
/**
* 通过分步查询查询员工信息
* @param emp_id
* @return
*/
Emp getEmpByStep(@Param("emp_id") int emp_id);
<resultMap id="empAndDeptByResultMap" type="Emp">
    <id column="empId" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
	<!--
	    property:当我们执行查询时候(第一个SQL语句),Mybatis会自动调用 dept属性中的方法
把查询结果赋值给dept属性
        select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId)这里在Dept中新建查询 获取返回值即可
        column:将sql以及查询结果中的某个字段设置为分步查询的条件;这里需要用到上一个(这里也就是第一个sql语句-第一个sql牵涉到了第二个,所以把第二个的column设置为第一个sql的查询结果字段)
	-->
	<association property="dept" select="com.atguigu.MyBatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="emp_id">
</association>
</resultMap> 
<!--Emp getEmpByStep(@Param("empId") int empId);-->
<select id="getEmpByStep" resultMap="empAndDeptByResultMap">
	select * from t_emp where emp_id = #{empId}
</select>

EmpMapper.xml

 <!--Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);-->
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
        select *from t_emp where empId=#{empId}
    </select>

DeptMapper.xml

<!-- Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId); 这个方法查询出来给property  
property="dept"(这个值会去调用select方法找到查询结果)而其中的deptId需要找到column中的empId(根据deptId查询的)
-->
<select id="getEmpAndDeptByStepTwo" resultType="Dept">
    select *from t_dept where dept_id=#{deptId}
</select>

image-20221227130240078

查询结果(两个查询信息):

DEBUG 12-27 13:39:26,762==>  Preparing: select * from t_emp where emp_id = ? (BaseJdbcLogger.java:137)
DEBUG 12-27 13:39:26,803====>  Preparing: select *from t_dept where dept_id=? (BaseJdbcLogger.java:137) 
Emp{empId=1, empName='张三', age=20, gender='男', dept=Dept{deptId=1, deptName='A'}}

②根据员工所对应的部门id查询部门信息
/**
* 分步查询的第二步: 根据员工所对应的dept_id查询部门信息
* @param dept_id
* @return
*/
Dept getEmpDeptByStep(@Param("dept_id") int dept_id);
<!--Dept getEmpDeptByStep(@Param("dept_id") int dept_id);-->
<select id="getEmpDeptByStep" resultType="Dept">
	select * from t_dept where dept_id = #{dept_id}
</select>  

8.3、一对多映射处理

一对一是实体类对象,一对多是集合(多对多也是)

image-20221227145708743

8.3.1、collection

/**
* 根据部门id查新部门以及部门中的员工信息
* @param dept_id
* @return
*/
Dept getDeptEmpBydept_id(@Param("dept_id") int dept_id);
<resultMap id="deptEmpMap" type="Dept">
    <id property="dept_id" column="deptId"></id>
    <result property="dept_name" column="deptName"></result>
    <!--
		ofType:设置collection标签所处理的集合属性中存储数据的类型(已经是一个集合了,不需要设置javaType了)
	-->
	<collection property="emps" ofType="Emp">
        <id property="emp_id" column="empId"></id>
        <result property="emp_name" column="empName"></result>
        <result property="age" column="age"></result>
        <result property="gender" column="gender"></result>
	</collection>
</resultMap>
<!--Dept getDeptEmpBydept_id(@Param("dept_id") int dept_id);-->
<select id="getDeptEmpBydept_id" resultMap="deptEmpMap">
	select dept.*,emp.* from t_dept dept left join t_emp emp on dept.dept_id =emp.dept_id where dept.dept_id = #{dept_id}
</select>

8.3.2、分步查询

image-20221227160457023

image-20221227160924123

image-20221227161016558

①查询部门信息

/**
* 分步查询部门和部门中的员工
* @param dept_id
* @return
*/
Dept getDeptByStep(@Param("dept_id") int dept_id);
<resultMap id="deptEmpStep" type="Dept">
      <id column="emp_id" property="empId"></id>
      <result column="emp_name" property="empName"></result>
    <collection property="emps" fetchType="eager"select="com.atguigu.MyBatis.mapper.EmpMapper.getEmpListBydeptId" column="dept_id">
	</collection>
</resultMap>
<!--Dept getDeptByStep(@Param("dept_id") int dept_id);-->
<select id="getDeptByStep" resultMap="deptEmpStep">
	select * from t_dept where dept_id = #{dept_id}
</select>

image-20221228111214737

②根据部门id查询部门中的所有员工

/**
* 根据部门id查询员工信息
* @param dept_id
* @return
*/
List<Emp> getEmpListBydept_id(@Param("dept_id") int dept_id);
<!--List<Emp> getEmpListBydept_id(@Param("dept_id") int dept_id);-->
<select id="getEmpListBydept_id" resultType="Emp">
	select * from t_emp where dept_id = #{dept_id}
</select>

分步查询的优点:可以实现延迟加载(开启了可以实现只执行员工的,不执行部门的SQL(第二条))

但是必须在核心配置文件中设置全局配置信息:

lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载(懒加载的目的是减少内存的浪费和减轻系统负担

aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性(调用员工或部门这里员工、部门都会加载)。否则,每个属性会按需加载

此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。默认是false

因为这是全局配置,有可能某一个查询需要不同于全局配置的加载,此时可通过association和

collection中的fetchType属性设置当前的分步查询是否使用延迟加载, fetchType="lazy(延迟加

载)|eager(立即加载)"

弹幕:一对多 多对一设置一个外键 多对多设置一个中间表 一对一也是外键 但是外键要唯一

一对一对应的是一个对象,一对多对应的是一个集合

EmpMapper.xml

  • resultType里面写的是具体类型

  • resultMap里面写的是 resultMap标签的Id,因为是一个自定义映射,所以需要我们指定哪一个标签对应哪一个属性

<?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.atguigu.mybatis.mapper.EmpMapper">

    <!--
        字段名和属性名不一致的情况,如何处理映射关系
        1、为查询的字段设置别名,和属性名保持一致
        2、当字段符合MySQL的要求使用_,而属性符合java的要求使用驼峰
        此时可以在MyBatis的核心配置文件中设置一个全局配置<setting>,可以自动将下划线映射为驼峰
        emp_id:empId,emp_name:empName
        3、使用resultMap自定义映射处理
        处理多对一的映射关系:
        1、级联方式处理
        2、association
        3、分步查询(比如要查的是员工信息,以及员工所对应的部门信息,那么可以先把员工给查出来,把员工对应部门的id作为条件,在部门表中查询,就能够查询出来他所对应的部门了)
        
        处理一对多的映射关系:
        1、collection
        2、分步查询
    -->

    <!--
        resultMap:设置自定义的映射关系
        id:唯一标识
        type:处理映射关系的实体类的类型
        常用的标签:
        id:处理主键和实体类中属性的映射关系
        result:处理普通字段和实体类中属性的映射关系
        association:处理多对一的映射关系(处理实体类类型的属性)
        collection:处理一对多的映射关系(处理集合类型的属性)
        column:设置映射关系中的字段名,必须是sql查询出的某个字段
        property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
    -->
    <resultMap id="empResultMap" type="Emp">
    <!--property对应我们type中Emp -->
        <id column="empId" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
    </resultMap>
	<!--写我们自定义映射resultMap的id-->
    <!--Emp getEmpByEmpId(@Param("empId") Integer empId);-->
    <select id="getEmpByEmpId" resultMap="empResultMap">
        select * from t_emp where emp_id = #{empId}
    </select>
	
    <select id="getEmpByEmpIdOld" resultType="Emp">
        <!--select emp_id empId,emp_name empName,age,gender from t_emp where emp_id = #{empId}-->
        select * from t_emp where emp_id = #{empId}
    </select>
	<!--第一种方法 级联方式,自定义映射,这里是把Emp表中的dept相关字段和我们dept中的属性连接起来-->
    <resultMap id="empAndDeptResultMapOne" type="Emp">
        <id column="empId" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <result column="dept_id" property="dept.deptId"></result>
        <result column="dept_name" property="dept.deptName"></result>
    </resultMap>
	<!--第二种-->
    <resultMap id="empAndDeptResultMap" type="Emp">
        <id column="empId" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <!--
            association:处理多对一的映射关系(处理实体类类型的属性)
            property:设置需要处理映射关系的属性的属性名
            javaType:设置要处理的属性的类型
        -->
        <!--javaType设置类型,在Mybatis范围之内都可以使用这个别名来表示具体类型-->
        <association property="dept" javaType="Dept">
        <!--id是主键,下面的是将它们和Dept属性中的属性进行映射-->
            <id column="dept_id" property="deptId"></id>
            <result column="dept_name" property="deptName"></result>
        </association>
    </resultMap>
<!--获取员工以及所对应的部门信息,通过一个sql把员工和部门都查出来,然后设置它们的映射关系-->
    <!--Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);-->
    <select id="getEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">
        select
        t_emp.*,t_dept.*
        from t_emp
        left join t_dept
        on t_emp.dept_id = t_dept.dept_id
        where t_emp.emp_id = #{empId}
    </select>

    <resultMap id="empAndDeptByStepResultMap" type="Emp">
        <id column="empId" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <!--
            property:设置需要处理映射关系的属性的属性名
            select:设置分步查询的sql的唯一标识
            column:将查询出的某个字段作为分步查询的sql的条件
            fetchType:在开启了延迟加载的环境中,通过该属性设置当前的分步查询是否使用延迟加载
            fetchType="eager(立即加载)|lazy(延迟加载)"
        -->
        <association property="dept" fetchType="eager"
                     select="com.atguigu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                     column="deptId"></association>
    </resultMap>

    <!--Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);-->
    <select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
        select * from t_emp where emp_id = #{empId}
    </select>

    <!--List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId);-->
    <select id="getDeptAndEmpByStepTwo" resultType="Emp">
        select * from t_emp where dept_id = #{deptId}
    </select>
    
</mapper>

两表连查共有七种结果

image-20221226171122566

这个查出来的是两个表中共同符合的数据,都会放到一起

image-20221226175525801

DeptMapper.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.atguigu.mybatis.mapper.DeptMapper">

    <!--Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);-->
    <select id="getEmpAndDeptByStepTwo" resultType="Dept">
        select * from t_dept where dept_id = #{deptId}
    </select>

    <resultMap id="deptAndEmpResultMap" type="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
        <!--
            ofType:设置集合类型的属性中存储的数据的类型
        -->
        <collection property="emps" ofType="Emp">
            <id column="empId" property="empId"></id>
            <result column="emp_name" property="empName"></result>
            <result column="age" property="age"></result>
            <result column="gender" property="gender"></result>
        </collection>
    </resultMap>

    <!--Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);-->
    <select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">
        SELECT *
        FROM t_dept
        LEFT JOIN t_emp
        ON t_dept.dept_id = t_emp.dept_id
        WHERE t_dept.dept_id = #{deptId}
    </select>

    <resultMap id="deptAndEmpResultMapByStep" type="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
        <collection property="emps"
                    select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
                    column="deptId"></collection>
    </resultMap>

    <!--Dept getDeptAndEmpByStepOne(@Param("deptId") Integer deptId);-->
    <select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpResultMapByStep">
        select * from t_dept where dept_id = #{deptId}
    </select>

</mapper>

9、动态SQL

补充:如果我们map接口的方法参数是list 或者数组 MyBatis都会把它放在map中的

  • list的话以list为键,list参数为值,放在map中(List<Emp> emps) ---不加@param时

  • 数组的话会以arr为键,当前参数为值(Integer[] empIds )所以访问时候要用arr ---不加@param时

  • 加了(@param("empIds") Integer[] empIds),@param注解的value属性值是谁就怎么访问这个数组

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了

解决 拼接SQL语句字符串时的痛点问题。(本质就是一系列标签)

比如一个表单有很多选项功能,我们可以选择设置or不设置,设置的话就需要拼接到SQL语句中;

如果我们没有传输请求参数,而我们在服务器却获取了这些请求参数,那我们获取的都null;

如果我们文本框什么都没却点击提交数据,那么提交到服务器的value属性就是空字符串;

单复选框没有被选中,也是null;

在服务器中判断下,如果它不为null或者空字符串,说明我们一定设置了这个条件,需要把它拼接到sql中;如果等于null或者空字符串的话,那说明我们没有设置这个条件

9.1、if

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之

标签中的内容不会执行

表中的id是没有意义的,我们不需要让用户知道id是几,也不需要让用户用它去查询数据,它只是能帮助我们精确的去操作数据

  • 在test中我们可以用传过来的信息,比如可以直接用下面Emp中的属性,进行判断等

  • 在xml中&&是一个特殊字符,所以我们要用and不能用&&表示and

  • where 1=1是为了防止后面条件都不满足的时候,where不会影响整体语句(这是第一种方法)

<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">
    select * from t_emp where 1=1
    <!--如果不是null不是空字符串,我们就把他拼接到sql中-->
    <if test="empName != '' and empName != null">
		 emp_name = #{empName}
	</if>
	<if test="age != '' and age != null">
		and age = #{age}
	</if>
	  <if test="gender != '' and gender != null">
            and gender= #{gender}
        </if>
</select>

9.2、where

where和if一般结合使用(第二种方法):

a>若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字

b>若where标签中的if条件满足则where标签会自动添加where关键字,并将条件最前方多余的

and去掉

注意:where标签不能去掉条件最后多余的and(ename = #{ename} and) 此时可以用trim标签

<!--List<Emp> getEmpByCondition(Emp emp);-->
 <select id="getEmpByCondition" resultType="Emp">
     select *from t_emp
     <where>
         <if test="empName != null and empName != ''">
             emp_name = #{empName} and
         </if>
         <if test="age != null and age != ''">
             age = #{age} and
         </if>
         <if test="gender != null and gender != ''">
             gender = #{gender}
         </if>
     </where>
 </select>

9.3、trim

trim用于去掉或添加标签中的内容

常用属性:

prefix:在trim标签中的内容的前面添加某些内容

prefixOverrides:在trim标签中的内容的前面去掉某些内容

suffix:在trim标签中的内容的后面添加某些内容

suffixOverrides:在trim标签中的内容的后面去掉某些内容

<select id="getEmpListByMoreTJ" resultType="Emp">
	select * from t_emp
	<trim prefix="where" suffixOverrides="and">
		<if test="empName != null and empName != ''">
			 emp_name = #{empName} and
		</if>
		<if test="age != '' and age != null">
			age = #{age} and
		</if>
		<if test="gender != '' and gender != null">
			gender = #{gender}
		</if>
	</trim>
</select>

9.4、choose、when、otherwise

choose、when(else if 至少有一个)、 otherwise(else)相当于if...else if..else(choose是父标签,when和otherwise都要写在它里面)

when元素表示当 when 中的条件满足的时候就输出其中的内容,跟 JAVA 中的 switch 效果差不多的是按照条件的顺序

  • 当所有的我很条件都不满足的时候就输出 otherwise 中的内容(可以不写)

  • 不需要加and,第一个条件满足下面都不用判断了

  • 这里员工姓名成立的话,其它下面所有条件都不再判断了

  • 当 when 中有条件满足的时候,就会跳出 choose,下面的不再判断,即所有的 when 和 otherwise 条件中,只有一个会输出

    当所有的条件都不满足的时候就输出 otherwise 中的内容。

  • 所有when里面满足一条则全部执行,全部不满足则不执行

<!--List<Emp> getEmpListByChoose(Emp emp);-->
<select id="getEmpListByChoose" resultType="Emp">
	select  from t_emp
	<where>
		<choose>
			<when test="empName!= '' and empName!= null">
				emp_name = #{empName}
			</when>
			<when test="age != '' and age != null">
				age = #{age}
			</when>
			<when test="gender != '' and gender != null">
				gender = #{gender}
			</when>
		</choose>
	</where>
</select>

9.5、foreach

  • 实现批量添加

  • 比如一个人对应多个角色就要用到批量添加

  • foreach中的collection="emps"这个collection设置我们当前要循环的集合或者数组

  • int insertMoreEmp(List<Emp> emps);这个不加@param的话,如果这是一个list集合,Mybatis会把它放在Map集合中,以list为键,以当前的参数为值(Mybatis把它放到Map中就叫list,小写) ----了解即可,还是加注解方便易懂

  • @param注解的value属性值是谁,collection中就写谁

  • item表示当前集合或者数组中的每一条数据

  • ,逗号不能在这里加(null,#{emp.ename},#{emp.age},#{emp.gender},#{emp.email},null) 这样循每一个()后会多出一个逗号,所以用separator分隔符

  • separator会自动在前后加一个空格,输入or的时候就放心了

<--第一种方式-->
<!--int insertMoreEmp(@param("emps") List<Emp> emps);-->
<!--这里先把部门设置为null-->
<insert id="insertMoreEmp">
        insert into t_emp values
        <foreach collection="emps" item="emp" separator=",">
            (null,#{emp.empName},#{emp.age},#{emp.gender},null)
        </foreach>
 </insert>
  <!--int deleteMoreEmp(@Param("empIds") Integer empIds);-->
<delete id="deleteMoreEmp">
	delete from t_emp where emp_id in
	<foreach collection="empIds" item="empId" separator=",">
		#{empId}
	</foreach>
</delete>
<--第二种方式-->
<!-- int deleteMoreEmp(@Param("empIds") Integer empIds); ()也可以通过这种方式写-->
<delete id="deleteMoreEmp">
	delete from t_emp where emp_id in
	<foreach collection="empIds" item="empId" separator="," open="(" close=")">
		#{empId}
	</foreach>
</delete>
      
<--第三种方式-->
    <!-- int deleteMoreEmp(@Param("empIds") Integer empIds); ()也可以通过加标签写 open:循环的所有内容以什么开始
        close:循环的所有内容以什么结束-->
 <delete id="deleteMoreEmp">
        delete from t_emp where
        <foreach collection="empIds" item="empId" separator="or">
            emp_id=#{empId}
        </foreach>
</delete>
    
    @Test
    public void testInsertMoreEmp(){
        SqlSession sqlSession= SqlSessionUtil.getSqlSession();
        DynamicSQLMapper mapper=sqlSession.getMapper(DynamicSQLMapper.class);
        Emp emp1=new Emp(null,"小明1",20,"男");
        Emp emp2=new Emp(null,"小明2",20,"男");
        Emp emp3=new Emp(null,"小明3",20,"男");
        List<Emp> list= Arrays.asList(emp1,emp2,emp3);
        mapper.insertMoreEmp(list);
    }
    @Test
    public void testDeleteMoreEmp(){
        SqlSession sqlSession= SqlSessionUtil.getSqlSession();
        DynamicSQLMapper mapper=sqlSession.getMapper(DynamicSQLMapper.class);
        Integer[] empIds=new Integer[]{6,7};
        mapper.deleteMoreEmp(empIds);
    }

9.6、SQL片段

sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入

empColumns emp中所有的字段

<sql id="empColumns">
	emp_id,ename,age,gender,dept_id
</sql>
select <include refid="empColumns"></include> from t_emp

DynamicSQL文件

DynamicSQLMapper.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.atguigu.mybatis.mapper.DynamicSQLMapper">

    <!--
        动态SQL:
        1、if,通过test属性中的表达式判断标签中的内容是否有效(是否会拼接到sql中)
        2、where
        a.若where标签中有条件成立,会自动生成where关键字
        b.会自动将where标签中内容前多余的and去掉,但是其中内容后多余的and无法去掉
        c.若where标签中没有任何一个条件成立,则where没有任何功能
        3、trim
        prefix、suffix:在标签中内容前面或后面添加指定内容
        prefixOverrides、suffixOverrides:在标签中内容前面或后面去掉指定内容
        4、choose、when、otherwise
        相当于java中的if...else if...else
        when至少设置一个,otherwise最多设置一个
        5、foreach
        collection:设置要循环的数组或集合
        item:用一个字符串表示数组或集合中的每一个数据,可以自定义
        separator:设置每次循环的数据之间的分隔符
        open:循环的所有内容以什么开始
        close:循环的所有内容以什么结束
        6、sql片段
        可以记录一段sql,在需要用的地方使用include标签进行引用(refid=reference id)
        <sql id="empColumns">
            emp_id,emp_name,age,gender,dept_id
        </sql>
        <include refid="empColumns"></include>
    -->

    <sql id="empColumns">
        emp_id,emp_name,age,gender,dept_id
    </sql>

    <!--List<Emp> getEmpByCondition(Emp emp);-->
    <select id="getEmpByCondition" resultType="Emp">
        select <include refid="empColumns"></include> from t_emp
        <trim prefix="where" suffixOverrides="and">
            <if test="empName != null and empName != ''">
                emp_name = #{empName} and
            </if>
            <if test="age != null and age != ''">
                age = #{age} and
            </if>
            <if test="gender != null and gender != ''">
                gender = #{gender}
            </if>
        </trim>
    </select>
    <select id="getEmpByConditionTwo" resultType="Emp">
        select * from t_emp
        <where>
            <if test="empName != null and empName != ''">
                emp_name = #{empName}
            </if>
            <if test="age != null and age != ''">
                and age = #{age}
            </if>
            <if test="gender != null and gender != ''">
                and gender = #{gender}
            </if>
        </where>
    </select>
    <select id="getEmpByConditionOne" resultType="Emp">
        select * from t_emp where 1=1
        <if test="empName != null and empName != ''">
            and emp_name = #{empName}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
        <if test="gender != null and gender != ''">
            and gender = #{gender}
        </if>
    </select>

    <!--List<Emp> getEmpByChoose(Emp emp);-->
    <select id="getEmpByChoose" resultType="Emp">
        select * from t_emp
        <where>
            <choose>
                <when test="empName != null and empName != ''">
                    emp_name = #{empName}
                </when>
                <when test="age != null and age != ''">
                    age = #{age}
                </when>
                <when test="gender != null and gender != ''">
                    gender = #{gender}
                </when>
            </choose>
        </where>
    </select>

    <!--void insertMoreEmp(@Param("emps") List<Emp> emps);-->
    <insert id="insertMoreEmp">
        insert into t_emp values
        <foreach collection="emps" item="emp" separator=",">
            (null,#{emp.empName},#{emp.age},#{emp.gender},null)
        </foreach>
    </insert>

    <!--void deleteMoreEmp(@Param("empIds") Integer[] empIds);-->
    <delete id="deleteMoreEmp">
        <!--delete from t_emp where emp_id in
        <foreach collection="empIds" item="empId" separator="," open="(" close=")">
            #{empId}
        </foreach>-->
        delete from t_emp where
        <foreach collection="empIds" item="empId" separator="or">
            emp_id = #{empId}
        </foreach>
    </delete>

</mapper>

DynamicSQLMapper.java

package com.atguigu.mybatis.mapper;

import com.atguigu.mybatis.pojo.Emp;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * Date:2022/6/30
 * Author:ybc
 * Description:
 */
public interface DynamicSQLMapper {

    /**
     * 根据条件查询员工信息
     * @param emp
     * @return
     */
    List<Emp> getEmpByCondition(Emp emp);

    /**
     * 使用choose查询员工信息
     * @param emp
     * @return
     */
    List<Emp> getEmpByChoose(Emp emp);

    /**
     * 批量添加员工信息
     * @param emps
     */
    void insertMoreEmp(@Param("emps") List<Emp> emps);

    /**
     * 批量删除
     * @param empIds
     */
    void deleteMoreEmp(@Param("empIds") Integer[] empIds);

}

DynamicMapperTest.java

package com.atguigu.mybatis.test;

import com.atguigu.mybatis.mapper.DynamicSQLMapper;
import com.atguigu.mybatis.pojo.Emp;
import com.atguigu.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

/**
 * Date:2022/6/30
 * Author:ybc
 * Description:
 */
public class DynamicMapperTest {

    @Test
    public void testGetEmpByCondition(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
        Emp emp = new Emp(null, "张三", 20, "");
        List<Emp> list = mapper.getEmpByCondition(emp);
        list.forEach(System.out::println);
    }

    @Test
    public void testGetEmpByChoose(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
        Emp emp = new Emp(null, "张三", 20, "");
        List<Emp> list = mapper.getEmpByChoose(emp);
        list.forEach(System.out::println);
    }

    @Test
    public void testInsertMoreEmp(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
        Emp emp1 = new Emp(null, "小明1", 20, "男");
        Emp emp2 = new Emp(null, "小明2", 20, "男");
        Emp emp3 = new Emp(null, "小明3", 20, "男");
        List<Emp> list = Arrays.asList(emp1, emp2, emp3);
        mapper.insertMoreEmp(list);
    }

    @Test
    public void testDeleteMoreEmp(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
        Integer[] empIds = new Integer[]{6,7};
        mapper.deleteMoreEmp(empIds);
    }

}

10、MyBatis的缓存

10.1、MyBatis的一级缓存

MyBatis是默认开启一级缓存的

一级缓存是SqlSession级别的,也就是通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问

使一级缓存失效的四种情况:

  1. 不同的SqlSession对应不同的一级缓存

  2. 同一个SqlSession但是查询条件不同

  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作会导致自动删除缓存(它就会从数据库中读取,而不是缓存中读取了)

  4. 同一个SqlSession两次查询期间手动清空了缓存

image-20221228161542075

10.2、MyBatis的二级缓存

Cache Hit Radio缓冲命中率:只要不是0就代表我们缓存被命中了(只有二级缓存才会输出缓存命中率)

sqlSession.clearCache()是针对一级缓存清空的(sqlSession级别),二级缓存不会被清空

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被

缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取

二级缓存开启的条件:

a>在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置

b>在映射文件中设置标签<cache/>

c>二级缓存必须在SqlSession关闭或提交之后有效(关闭后才是二级缓存,否则还是一级)

d>查询的数据所转换的实体类类型必须实现序列化的接口(也就是相应的接口要实现序列化Serializable)否则报错

使二级缓存失效的情况:

两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

image-20221228162200849

10.3、二级缓存的相关配置

在mapper配置文件中添加的cache标签可以设置一些属性:

①eviction属性:缓存回收策略,默认的是 LRU。

LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。

FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。

SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

②flushInterval属性:刷新间隔,单位毫秒

默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新

③size属性:引用数目,正整数(缓存中最多能存储多少对象,一般不用设置,默认即可,太大内存容易溢出,太小没意义)

代表缓存最多可以存储多少个对象,太大容易导致内存溢出

④readOnly属性:只读, true/false

true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了 很重要的性能优势。

false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是

false。(这些都不会改数据库里面的对象)

弹幕:当readOnly的值为true时,我们的实体类就不需要实现序列化接口了

10.4、MyBatis缓存查询的顺序

先查询二级缓存,因为二级缓存中可能会有其他程序如一级SqlSession,一级关闭保存到二级缓存中了)已经查出来的数据,可以拿来直接使用(比一级缓存存的数据多)。

如果二级缓存没有命中,再查询一级缓存(说明一级缓存没有关闭,二级缓存中也没有)

如果一级缓存也没有命中,则查询数据库

SqlSession关闭之后,一级缓存中的数据会写入二级缓存

10.5、整合第三方缓存EHCache

  • 针对于二级缓存的,了解即可

10.5.1、添加依赖

<!-- Mybatis EHCache整合包 -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

10.5.2、各jar包功能

jar包名称作用
mybatis-ehcacheMybatis和EHCache的整合包
ehcacheEHCache核心包
slf4j-apiSLF4J日志门面包
logback-classic支持SLF4J门面接口的一个具体实现

10.5.3、创建EHCache的配置文件ehcache.xml

<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
	<!-- 磁盘保存路径 -->
	<diskStore path="D:\atguigu\ehcache"/>
	<defaultCache
        maxElementsInMemory="1000"
        maxElementsOnDisk="10000000"
        eternal="false"
        overflowToDisk="true"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
	</defaultCache>
</ehcache>

10.5.4、设置二级缓存的类型

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

10.5.5、加入logback日志

存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。 创建logback的配置文件logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
	<!-- 指定日志输出的位置 -->
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
            <!-- 日志输出的格式 -->
            <!-- 按照顺序分别是: 时间、日志级别、线程名称、打印日志的类、日志主体内容、换行-->
			<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger][%msg]%n</pattern>
		</encoder>
	</appender>
    <!-- 设置全局日志级别。日志级别按顺序分别是: DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
		<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
		<appender-ref ref="STDOUT" />
	</root>
	<!-- 根据特殊需求指定局部日志级别 -->
	<logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>

10.5.6、EHCache配置文件说明

属性名是否必须作用
maxElementsInMemory在内存中缓存的element的最大数目
maxElementsOnDisk在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效, 如果为false那么还要根据timeToIdleSeconds、timeToLiveSeconds判断
overflowToDisk设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
timeToIdleSeconds当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时, 这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMBDiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
diskPersistent在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskExpiryThreadIntervalSeconds磁盘缓存的清理线程运行间隔,默认是120秒。每个120s, 相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU (最近最少使用),可选的有LFU (最不常使用)和FIFO (先进先出)

11、MyBatis的逆向工程

正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。 Hibernate是支持正向工

程的。

逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:

  • Java实体类

  • Mapper接口

  • Mapper映射文件

11.1、创建逆向工程的步骤

Mybatis-逆向工程 MyBatis Generator: 简称MBG,

①添加依赖和插件

<!-- 依赖MyBatis核心包 -->
<dependencies>
	<dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
	</dependency>
	<!-- junit测试 -->
	<dependency>
	<groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
	</dependency>
	<!-- log4j日志 -->
	<dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
	</dependency>
	<dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
	</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
	<!-- 构建过程中用到的插件 -->
	<plugins>
		<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
		<plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.0</version>
			<!-- 插件的依赖 -->
			<dependencies>
				<!-- 逆向工程的核心依赖 -->
				<dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.2</version>
				</dependency>
				<!-- MySQL驱动,这仅是插件里面设置了驱动,工程里面还要有这个驱动 -->
				<dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.16</version>
				</dependency>
			</dependencies>
		</plugin>
	</plugins>
</build>

②创建MyBatis的核心配置文件

③创建逆向工程的配置文件

新建文件new file

文件名必须是:generatorConfig.xml

逆向工程基本都是对单表有用,对多表或者复杂的sql是不能用的,但我们基本都是单表操作

我们执行CRUD时候输出的是对象地址,我们需要主动添加toString方法才能打印输出表中内容,构造方法我们也需要自己添加(需要时候再添加)

<?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>
	<!--
			targetRuntime: 执行生成的逆向工程的版本
                MyBatis3Simple: 生成基本的CRUD(清新简洁版)
                MyBatis3: 生成带条件的CRUD(奢华尊享版-基本可以实现所以的单表操作-多了一个Example,可以写条件)
			  targetRuntime:逆向工程的版本
	-->
	<context id="DB2Tables" targetRuntime="MyBatis3">
		<!-- 数据库的连接信息,只有连接上数据库,才能获取相应的信息,生成文件 -->
		<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
						connectionURL="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"
						userId="root"
						password="123456">
		</jdbcConnection>
		<!-- targetPackage:javaBean(实体类)的生成策略(表示往哪儿生成)  targetProject=".\src\main\java"生成到当前工程下的src的java中-->
        <!--enableSubPackages是否能够生成子包,每一个点.都是一层目录,如果设置为false,这个点不能被解析成目录,只会当成一个目录的名字-->
        <!--trimStrings,当前表中有什么,生成所对应的实体类就是什么,trimStrings能够把这个字段前后的空格给去掉生成相对应实体类中的属性-->
		<javaModelGenerator targetPackage="com.atguigu.mybatis.pojo" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
		</javaModelGenerator>
		<!-- SQL映射文件的生成策略 -->
		<sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper"targetProject=".\src\main\resources">
			<property name="enableSubPackages" value="true" />
		</sqlMapGenerator>
		<!-- Mapper接口的生成策略 -->
		<javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\java">
			<property name="enableSubPackages" value="true" />
		</javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName表名设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名,Mapper接口和映射文件都会根据它来起名,比如t_emp这个表会生成EmpMapper名字的映射文件 -->
        <table tableName="t_emp" domainObjectName="Emp"/>
        <table tableName="t_dept" domainObjectName="Dept"/>
	</context>
</generatorConfiguration>

④执行MBG插件的generate目标

3

⑤效果

4

 <id column="emp_id" property="empId" jdbcType="INTEGER" />  jdbcType表示当前字段类型,可以省略,前面我们都没写,这里自动生成的有
emp_id = #{empId,jdbcType=INTEGER}  jdbcType表示当前字段的类型,可以省略

通过条件来获取当前的总记录数

 int countByExample(EmpExample example);

根据条件进行删除

  int deleteByExample(EmpExample example);可以根据任意条件进行删除

根据主键进行删除

int deleteByPrimaryKey(Integer empId); 只能根据empId来删除

添加

    int insert(Emp record); 比如我们字段中有一个为null,我们添加时候也会添加一个null,比如emp_name我们赋值的时候还会添加null

选择性添加(mysql创建表时候默认值就是null,如果我们选择int insert()我们会把当前属性的值为null赋值给当前字段,不管默认值是什么,都赋值null,而insertSelective不会为null赋值,用的是默认值)

 int insertSelective(Emp record); 如果哪一个属性为null,它就不会为其赋值

根据任意字段去查

  List<Emp> selectByExample(EmpExample example);

根据主键去查

 Emp selectByPrimaryKey(Integer empId);

根据条件选择性修改(如果某个字段为null,它不会去修改它所对应的字段)

  int updateByExampleSelective(@Param("record") Emp record, @Param("example") EmpExample example);

根据条件来进行修改(如果我们执行的sql语句中某个字段为null-没有设置,它也会把字段修改为null;比如有name这个属性,我们没有设置name,它会把它改为null,而选择性修改则不会)

  int updateByExample(@Param("record") Emp record, @Param("example") EmpExample example);

根据主键选择性修改(我们sql语句中如果没有相关的字段,是不会把它修改为null的,不会动它)

   int updateByPrimaryKeySelective(Emp record);

根据主键来修改

 int updateByPrimaryKey(Emp record);

根据条件来查,如果条件设置空null,那么就是没有条件,会查出来所有数据

image-20221228203301287

11.2、QBC查询

QBC(QueryByCriteria) 根据条件来查询

  • example.createCriteria().and…是创建条件对象,点.能出来很多,都是以and开头的,根据员工姓名我们就写andEmp…它会出现很多

  • image-20221228204528302

  • example.or().andGenderEqualTo("男")它会主动和上一个我们写的条件以or进行连接,

  • image-20221228204937840

@Test
public void testMBG(){
    try {
		InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new
		SqlSessionFactoryBuilder().build(is);
		SqlSession sqlSession = sqlSessionFactory.openSession(true);
		EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        //查询所有数据
        /*List<Emp> list = mapper.selectByExample(null);
        list.forEach(emp -> System.out.println(emp));*/
        //根据条件查询。需要什么条件就创建什么条件
        /*EmpExample example = new EmpExample();
        example.createCriteria().andEmpNameEqualTo("张三").andAgeGreaterThanOrEqualTo(20);
        example.or().anddept_idIsNotNull();
		List<Emp> list = mapper.selectByExample(example);
        list.forEach(emp -> System.out.println(emp));*/
        mapper.updateByPrimaryKeySelective(newEmp(1,"admin",22,null,"456@qq.com",3));
	} catch (IOException e) {
		e.printStackTrace();
	}
}

12、分页插件

limit index,pageSize

pageSize:每页显示的条数(浏览器传到服务器的请求,已知条件)

pageNum:当前页的页码(浏览器传到服务器的请求,已知条件)

index:当前页的起始索引,index=(pageNum-1)*pageSize (你访问的是第一页,当前页的起始索引就是0)

count:总记录数

totalPage:总页数

totalPage = count / pageSize;

if(count % pageSize != 0){

totalPage += 1; //还需要在加上一页展示最后的信息

}

limit

pageSize=4,pageNum=1,index=0 limit 0,4

pageSize=4,pageNum=3,index=8 limit 8,4

pageSize=4,pageNum=6,index=20 limit 8,4(前两页已经放9条数据了,这从8开始)

应该有: 首页 上一页 2 3 4 5 6 下一页 末页(在第一页不用展示首页、上一页,末页一样)

12.1、分页插件的使用步骤

pagehelper,配置成功如果没有相应的类,则重启idea

①添加依赖

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

②配置分页插件

在MyBatis的核心配置文件中有专门配置插件的标签

Interceptor是拦截器的意思,当我们实现查询功能时候,它会去自动拦截查询功能,然后在其中加入limit关键字,获取分页相关的数据

<plugins>
    <!--设置分页插件-->
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>	

image-20221228220744075

双击搜索.PageInterceptor也是能够找到相应的插件类

image-20221228223416883

12.2、分页插件的使用

a>在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能

pageNum:当前页的页码

pageSize:每页显示的条数

b>在查询获取list集合之后,使用PageInfo<T> pageInfo = new PageInfo<>(List<T> list, int

navigatePages)获取分页相关数据

list:分页之后的数据,如果limit设置一个数字,比如limit 3 默认是第一页,显示4条(相当于limit(0,4))

navigatePages:导航分页的页码数

c>分页相关数据

PageInfo{

pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,

list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,

pages=8, reasonable=false, pageSizeZero=false},

prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,

hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,

navigatepageNums=[3,4, 5, 6, 7,]

}

pageNum:当前页的页码

pageSize:每页显示的条数

size:当前页显示的真实条数

total:总数据记录数

pages:总页数 total/size向上取整

prePage:上一页的页码

nextPage:下一页的页码

startRow, endRow当前页开始和结束的行数

isFirstPage/isLastPage:是否为第一页/最后一页

hasPreviousPage/hasNextPage:是否存在上一页/下一页

navigatePages:导航分页的页码数

navigateFirstPage4, navigateLastPage8,表示我们当前导航分页从第一页开始,第8页结束

  • PageInfo比Page里面的内容更多,封装的有Page对象

  • size显示的和pageSize不一样,因为如果是最后一页,可能会少几条(又叫当前页显示的真实数据)

  • totalPage = count / pageSize;

    if(count % pageSize != 0){

    totalPage += 1; //还需要在加上一页展示最后的信息

    }

navigatepageNums:设置后会自动生成导航分页的页码,[1,2,3,4,5]

  • PageInfo<Emp> pageInfo = new PageInfo<>(list, 5);这个5就是导航分页的页码数navigatepageNums,写5之后就会在中间显示5个页码

  • 以当前页结束-2位置,到当前页减2结束

Page<E> extend ArrayList<E> implements Closeable --了解即可

PageTest.java

package com.atguigu.mybatis.test;

import com.atguigu.mybatis.mapper.EmpMapper;
import com.atguigu.mybatis.pojo.Emp;
import com.atguigu.mybatis.utils.SqlSessionUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/**
 * Date:2022/6/30
 * Author:ybc
 * Description:
 */
public class PageTest {

    /**
     * PageInfo{
     * pageNum=1, pageSize=4, size=4,
     * startRow=1, endRow=4, total=30,
     * pages=8,
     * System.out.println(page);能输出这些信息
     * list=Page{count=true, pageNum=1, pageSize=4, startRow=0, endRow=4, total=30, pages=8, reasonable=false, pageSizeZero=false}[Emp{empId=1, empName='aaa', age=null, gender='null', deptId=null}, Emp{empId=2, empName='a', age=null, gender='null', deptId=null}, Emp{empId=3, empName='a', age=null, gender='null', deptId=null}, Emp{empId=4, empName='a', age=null, gender='null', deptId=null}],
     * prePage=0, nextPage=2, isFirstPage=true,
     * isLastPage=false, hasPreviousPage=false,
     * hasNextPage=true, navigatePages=5,
     * navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]}
     */

    @Test
    public void testPage(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        //查询功能之前开启分页功能
        //PageHelper.startPage(5, 4);
        //获取分页对象,只有一部分数据
        Page<Object> page = PageHelper.startPage(5, 4);
        List<Emp> list = mapper.selectByExample(null);
        //PageInfo<Emp>泛型指我们当前查询的类型要转换为的实体类型
        //查询功能之后可以获取分页相关的所有数据
        PageInfo<Emp> pageInfo = new PageInfo<>(list, 5);
        list.forEach(System.out::println);
        System.out.println(pageInfo);
    }
}
文章来源:https://blog.csdn.net/m0_64880608/article/details/135097706
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。