freyhe

导航

MyBatis

0.前置知识

1.JdbcTemplate:Spring封装了JDBC

image-20220220145455826

2.Hibernate:全自动全映射

image-20220220145514260

3.MyBatis:半自动,轻量级的框架

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

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

MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO映射成数据库中的记录.

1.MyBatis简介

MyBatis 是由 Ibatis 发展而来的,Ibatis1.x 和 Ibatis2.x,称之为 Ibatis,

Ibatis3.x 改称为:MyBatis。MyBatis 位于软件三层架构中的 dao 层。

MyBatis 官网:https://github.com/mybatis/mybatis-3

2.MyBatis 的优势[常见面试题]

第一个:MyBatis 本身是一个框架,除了可以对表中的数据进行增删改查操作之外,还有缓存、字段映射等机制。

第二个:MyBatis 支持动态 SQL[根据不同参数生成不同的 SQL 语句]

第三个:MyBatis 支持将 java 代码和 SQL 语句进行分离。

第四个:MyBatis 支持将表的关联关系直接映射为 pojo 对象的关联关系。

第五个:MyBatis 是一个半自动化的 ORM 框架。

ORM:Object Relational Mapping 对象关系映射

image-20220220150633414

image-20220220150725905

3.MyBatis 的第一个案例

第一步:创建动态web工程,导入jar包:mysql、mybatis

第二步:创建mybatis的全局配置文件mybatis-config.xml一个项目中只有一个全局配置文件

​ 全局配置文件中配置的是:数据库连接信息、加载了SQL映射文件、事务管理器信息等...系统运行环境信息

第三步:创建SQL映射文件

​ 一个项目中可以有多个SQL映射文件

​ SQL映射文件主要写对数据表增删改查操作的SQL语句

第四步:写测试代码:

​ 1.加载mybatis的全局配置配置文件生成SqlSessionFactory对象

​ 2.根据SqlSessionFactory的openSession方法得到SqlSession对象

​ 3.通过SqlSession对象完成对数据表的增删改查操作

​ 4.释放资源

代码示例(详见源码解析)

版本1 原生写法

public class AppTest {
    @Test
    public void testFirstMyBatisDemo() throws IOException {
        //1.第一步:加载mybatis的全局配置配置文件生成SqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2.根据SqlSessionFactory的openSession方法得到SqlSession对象
        //SqlSession代表服务器和数据库一次会话。对数据表的增删改查操作都是通过SqlSession对象完成的
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3.通过SqlSession对象完成对数据表的增删改查操作
        Book book = sqlSession.selectOne("org.mybatis.example.BlogMapper.selectBlog", 5);
        System.out.println(book);
        //4.释放资源
        sqlSession.close();
    }
}

全局配置文件mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">     //数据库连接信息
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/bookdb?rewriteBatchedStatements=true"/>
                <property name="username" value="root"/>
                <property name="password" value="ok"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="book.xml"/> //去src路径下寻找
    </mappers>
</configuration>

SQL映射配置文件book.xml(可以有多个类.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:名称空间;指定为接口的全类名
//id:唯一标识
//resultType:返回值类型
//#{d}:从传递过来的参数中取出id值
<mapper namespace="org.mybatis.example.BlogMapper">
    //sql语句,测试类里通过namespace.id来找到对于的sql语句 例如:org.mybatis.example.BlogMapper.selectBlog
    <select id="selectBlog" resultType="com.atguigu.pojo.Book">
      select * from t_book where id = #{id}
    </select>
</mapper>

版本二 接口式编程

1、接口式编程 (底层使用动态代理,本质是生成实现同一接口的代理类对象)

原生: Dao接口 ====> DaoImpl

mybatis: Mapper接口 ====> xxMapper.xml

2、SqlSession代表和数据库的一次会话;用完必须关闭;

3、SqlSession(底层就是connection)和connection一样 都是非线程安全。每次使用都应该去获取新的对象

​ 不关闭会产生资源竞争,A线程使用的SqlSession未关闭,B线程也拿来用,实际B线程应该使用新的SqlSession

4、mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。(将接口和xml进行绑定)

​ BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);

5、两个重要的配置文件:

mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等...系统运行环境信息

sql映射文件:保存了每一个sql语句的映射信息:将sql抽取出来(一定要有)

接口代码

public interface BookMapper {
    public Book getBookById(Integer id);
}

book.xml

@Test
public void test02() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    try (SqlSession sqlSession = sqlSessionFactory.openSession();) {
        BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
        Book book = bookMapper.getBookById(5);

        System.out.println(bookMapper.getClass());//class com.sun.proxy.$Proxy4代理类对象
        System.out.println(book);
    }
}

4.MyBatis-全局配置文件mybatis-config.xml[不重要]

MyBatis 的配置文件mybatis-config.xml包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息。

文档的顶层结构如下

必须按顺序配置,否则报错

  • configuration 配置

  • properties 属性

  • settings 设置

  • typeAliases 类型命名

  • typeHandlers 类型处理器

  • objectFactory 对象工厂

  • plugins 插件

  • environments 环境

    • environment 环境变量
    • transactionManager 事务管理器
    • dataSource 数据源
  • databaseIdProvider 数据库厂商标识 :SQL语句使用databaseId属性引用

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

     <!--
       1.properties标签是用来加载properties文件的
          resource:默认就是去加载src目录下的properties文件
          url:本地磁盘或者网络资源
     -->
    <properties resource="jdbc.properties" ></properties>


    <!--类型别名处理器:实际开发中,不建议使用。 -->
    <typeAliases>
        <!-- 该typeAlias标签可以为类型起别名,
              type:指定类的全类名,默认的别名是:简类名,别名不区分大小写
              alias:指定类的别名,一旦自己指定了别名,再使用别名的时候,就只能使用自己指定的别名
             批量起别名:package,指定一个包名,表示为这个包下的所有类都起一个别名,默认的别名是:简类名
        -->
        <package name="com.atguigu.bean"/>
    </typeAliases>
    <typeHandlers>
        <!--1、配置我们自定义的TypeHandler  -->
        <typeHandler handler="com.atguigu.mybatis.typehandler.MyEnumEmpStatusTypeHandler" javaType="com.atguigu.mybatis.bean.EmpStatus"/>
        <!--2、也可以在处理某个字段的时候告诉MyBatis用什么类型处理器
    保存:#{empStatus,typeHandler=xxxx}
    查询:
     <resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmp">
       <id column="id" property="id"/>
       <result column="empStatus" property="empStatus" typeHandler=""/>
      </resultMap>
    注意:如果在参数位置修改TypeHandler,应该保证保存数据和查询数据用的TypeHandler是一样的。
    -->
    </typeHandlers>

    <!--
      1.environments标签的default属性值:可以切换为environment标签的id值,表示当前使用的是哪种环境
       在每个environment标签底下又有transactionManager和dataSource两个子标签:
              transactionManager:标签进行事务管理,type属性指定管理事务的类
                             取值:
                                 JDBC:  支持事务
                                 MANAGED:不支持事务
              dataSource:
                     取值:      
                        UNPOOLED:不使用连接池,UnpooledDataSourceFactory
                        POOLED:使用连接池, PooledDataSourceFactory
                        JNDI:(过时的)在EJB 或应用服务器这类容器中查找指定的数据源
                        自定义:实现DataSourceFactory接口,定义数据源的获取方式。
    -->
    <environments default="development">
        <!-- 1.开发人员使用的开发环境:开发人员的数据库 -->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driverClass}"/>
                <property name="url" value="${jdbc.jdbcUrl}"/>
                <property name="username" value="${jdbc.userName}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <!-- 2.测试人员使用的测试环境:测试人员的数据库 -->
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.test.driverClass}"/>
                <property name="url" value="${jdbc.test.jdbcUrl}"/>
                <property name="username" value="${jdbc.test.userName}"/>
                <property name="password" value="${jdbc.test.password}"/>
            </dataSource>
        </environment>
        <!-- 3.运维人员使用的生产|线上环境:运维人员的数据库 -->
        <environment id="product">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.product.driverClass}"/>
                <property name="url" value="${jdbc.product.jdbcUrl}"/>
                <property name="username" value="${jdbc.product.userName}"/>
                <property name="password" value="${jdbc.product.password}"/>
            </dataSource>
        </environment>
        <!-- 4.oracle数据库 -->
        <environment id="dev_oracle">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${orcl.driver}" />
                <property name="url" value="${orcl.url}" />
                <property name="username" value="${orcl.username}" />
                <property name="password" value="${orcl.password}" />
            </dataSource>
        </environment>
        <!-- 数据库厂商标识,MyBatis 可以根据不同的数据库厂商执行不同的语句 -->
        <!-- SQL语句使用databaseId属性引用 -->
        <databaseIdProvider type="DB_VENDOR">
            <property name="MySQL" value="mysql"/>
            <property name="Oracle" value="oracle"/>
            <property name="SQL Server" value="sqlserver"/>
        </databaseIdProvider>
    </environments>

    <!--
        mappers里面可以有多个mapper标签,每个mapper标签都可以加载SQL映射文件或者Mapper接口
        每个mapper标签:
               属性:
                   resource:用来加载SQL映射文件
                   url:一般不用
                   class:加载Mapper接口,有两个要求:
                      要求:1.Mapper接口和SQL映射文件必须在同一个包下
                            2.要求Mapper接口的名字和SQL映射文件的名字必须相同。
            package标签:批量加载mapper接口
                      要求:1.Mapper接口和SQL映射文件必须在同一个包下
                            2.要求Mapper接口的名字和SQL映射文件的名字必须相同。
     -->
    <mappers>
        <package name="com.atguigu.dao"/>
    </mappers>
</configuration>

1.properties属性

​ 如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:

​ 1.在 properties 元素体内指定的属性首先被读取。

​ 2.然后根据 properties 元素中的 resource 属性读取类路径下属性文件

	或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。

​ 3.最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。

<properties resource="db.properties"></properties>
...
<dataSource type="com.atguigu.datasource.MyDruidDataSource">
   <property name="driver" value="${jdbc.driverClass}"/>
   <property name="url" value="${jdbc.jdbcUrl}"/>
   <property name="username" value="${jdbc.userName}"/>
   <property name="password" value="${jdbc.password}"/>
</dataSource>

2.settings设置

image-20220218073504931

<settings>
    <!-- 能将数据表中以_分隔的列名(a_column) 自动转换为pojo类的驼峰式的属性名aColumn -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

3.typeAliases别名处理器

类型别名是为 Java 类型设置一个短的名字,可以方便我们引用某个类。

image-20220218073839602

类很多的情况下,可以批量设置别名这个包下的每一个类创建一个默认的别名,就是简单类名小写。

image-20220218073901797

也可以使用@Alias注解为其指定一个别名

image-20220218073923134

不建议使用别名,使用全类名,便于查看SQL语句是怎么被封装为JAVA对象的

MyBatis已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,我们在起别名的时候千万不要占用已有的别名。

image-20220218073953436

4.typeHandlers类型处理器

作用:将数据库中的类型转换为对应的 java 对象中的类型。我们直接使用即可(自动调用)

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的

方式转换成 Java 类型。

image-20220219183734003

日期类型的处理

日期和时间的处理,JDK1.8以前一直是个头疼的问题。

我们通常使用JSR310规范领导者StephenColebourne创建的Joda-Time来操作。

1.8已经实现全部的JSR310规范了。

日期时间处理上,我们可以使用MyBatis基于JSR310(Date and Time API)编写的各种日期时间类型处理器。

MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的

image-20220219183930448

自定义类型处理器

我们可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。

步骤:

1)实现org.apache.ibatis.type.TypeHandler接口或者继承org.apache.ibatis.type.BaseTypeHandler

2)指定其映射某个JDBC类型(可选操作)

3)在mybatis全局配置文件中注册

5.objectFactory

作用:MyBatis 框架底层就是用该工厂对象创建 resultType 值对应的 pojo 对象。最终也是通过反射技术实现的。

6.plugins插件

插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。

插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。

​ 四大对象及其调用的重要方法
​ Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed)
​ StatementHandler (prepare, parameterize, batch, update, query)
​ ParameterHandler (getParameterObject, setParameters)
​ ResultSetHandler (handleResultSets, handleOutputParameters)

7.environments环境

nvironments标签中有多个enviroment标签,每个enviroment都对应一种环境。例如 开发环境,测试环境,线上环境

每种环境使用一个environment标签进行配置并指定唯一标识符

可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境

1.environment-指定具体环境

<environments default="test">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${jdbc.driverClass}"/>
            <property name="url" value="${jdbc.jdbcUrl}"/>
            <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="${jdbc.driverClass}"/>
            <property name="url" value="${jdbc.jdbcUrl}"/>
            <property name="username" value="${jdbc.userName}"/>
            <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>
</environments>

2.transactionManager

type: JDBC | MANAGED | 自定义 (Configuration类中)

JDBC:使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围。JdbcTransactionFactory

MANAGED:不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 ManagedTransactionFactory

自定义:实现TransactionFactory接口,type=全类名/别名

3.dataSource

type: UNPOOLED | POOLED | JNDI | 自定义

​ UNPOOLED:不使用连接池,UnpooledDataSourceFactory

​ POOLED:使用连接池, PooledDataSourceFactory

​ JNDI: 在EJB 或应用服务器这类容器中查找指定的数据源

​ 自定义:实现DataSourceFactory接口,定义数据源的获取方式。

实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置

4.databaseIdProvider环境为数据库服务器起别名

MyBatis 可以根据不同的数据库厂商执行不同的语句。

​ Type: DB_VENDOR

使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider接口来自定义。

​ Property-name:数据库厂商标识

​ Property-value:为标识起一个别名,方便SQL语句使用databaseId属性引用

<databaseIdProvider type="DB_VENDOR">
   <property name="SQL Server" value="sqlserver"/>
   <property name="DB2" value="db2"/>
   <property name="Oracle" value="oracle" />
   <property name="MySQL" value="mysql" />     <!--value值是别名-->
</databaseIdProvider>

sql语句可以根据别名指定数据库服务器

image-20220219184346716

DB_VENDOR

会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。

由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短

MyBatis匹配规则如下:

1、如果没有配置databaseIdProvider标签,那么databaseId=null

2、如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为null

3、如果databaseId不为null,他只会找到配置databaseId的sql语句

4、MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库databaseId 属性的所有语句。

​ 如果同时找到带有 databaseId 和不带databaseId 的相同语句,则后者会被舍弃。

5.SQL 映射文件xxMapper

0.1关于XML文件使用"<" 和 "&"的补充说明

在 XML 元素中,"<" 和 "&" 是非法的。

"<" 会产生错误,因为解析器会把该字符解释为新元素的开始。

"&" 也会产生错误,因为解析器会把该字符解释为字符实体的开始。

在mapper文件中写sql语句时,遇到特殊字符时,例如:&& ,“”,< ....特殊字符,有两种解决方案:

第一种:使用特殊字符的实体名称(HTML ISO-8859-1 参考手册)

结果 描述 实体名称
" quotation mark &quot;
' apostrophe &apos;
& ampersand &amp;
< less-than &lt;
> greater-than &gt;

第二种:如:< 等,建议使用<![CDATA[ sql 语句 ]]>标记,将sql语句包裹住,不被解析器解析

image-20220219204617032

图中要实现 a.scanTime < concat(.....),就需要图中的写法 time <![CDATA[ 小于号 ]]> concat(......)

0.2如何引入mapper文件

mappers标签中可以有多个mapper标签

mapper标签:加载sql映射文件和mapper接口

属性:

​ resource:默认是加载类路径(src目录下)的sql映射文件

​ url:加载网络上的某个sql映射文件[不用]

​ class:加载Mapper接口,要求:

​ 1.mapper接口和对应的SQL映射文件要在同一个包下

​ 2.mapper接口和对应的SQL映射文件必须是同名

<mappers>
   <mapper class="com.atguigu.dao.BookMapper"/>
</mappers>

package标签:批量加载mapper接口,要求 :SQL映射文件和Mapper接口必须同包同名

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

1.全部标签介绍

内部标签是无序的

•cache –命名空间的二级缓存配置

•cache-ref – 其他命名空间缓存配置的引用。

•resultMap – 自定义结果集映射

•parameterMap – 已废弃!老式风格的参数映射

•sql –抽取可重用语句块。

•insert – 映射插入语句

•update – 映射更新语句

•delete – 映射删除语句

•select – 映射查询语句

1.增删改查标签

使用原生的mybatis(不结合Spring),对于增删改操作:需要提交事务
1.手动提交:sqlSession.commit();
2.自动提交:sqlSessionFactory.openSession(true)

代码示例

Mapper 接口:

public interface BooksMapper {
    public Book getBookById(Integer bid);
    public void insertBook(Book book);
    public void updateBook(Book book);
    public void deleteBookByBid(Integer bid);
}

SQL 映射文件:

<mapper namespace="com.atguigu.dao.BooksMapper">
    <!--该标签的作用:做结果映射 -->
    <resultMap id="getBookByIdResultMap" type="com.atguigu.bean.Book">
        <!-- 主键映射用id标签-->
        <result column="id" property="id"/>
        <!-- 普通字段用result标签映射-->
        <result column="title" property="title"/>
        <!-- 当表的列名和pojo对象的属性名一致的情况下,resultMap能自动完成映射-->
        <result column="img_path" property="imgPath"/>
    </resultMap>
    <!--
     resultType和resultMap都是处理sql语句返回结果,只能同时存在一个属性。
     -->
    <select id="getBookById" parameterType="java.lang.Integer"  resultMap="getBookByIdResultMap">
        select id,title,author,price,sales,stock,img_path from ${name} where title like '${condition}' order by ${orderByField} desc limit 0,1
    </select>
    <insert id="insertBook">
        insert into book values(null,#{title},#{author},#{price},#{sales},#{stock},#{imgPath})
    </insert>
    <update id="updateBook" >
        update book set title=#{title} where id =#{id}
    </update>
    <delete id="deleteBookByBid" >
        delete from book where id = #{bid}
    </delete>
    <!-- sql片段:做sql语句重用 -->
    <sql id="SqlFrament">
        id,title,author,price,sales,stock,img_path
    </sql>
</mapper>

2.sql片段标签:做sql语句重用

1、sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用

2、include来引用已经抽取的sql:

3、include还可以自定义一些property,sql标签内部就能使用自定义的属性

​ include-property:取值的正确方式${prop},

​ #{不能使用这种方式}

<!-- sql片段:做sql语句重用 --> 
<sql id="SqlFrament"> id,title,author,price,sales,stock,img_path </sql>

在使用到的地方通过 include 标签引入即可:

<select id="getBookById" parameterType="java.lang.Integer" resultType="com.atguigu.bean.Book">
 select <include refid="SqlFrament"/> from book where id = #{bid} </select>

3.resultMap 标签:结果集映射

ResultMap 标签作用:

1.做结果映射

2.将有关联关系的表的数据直接映射为 POJO 对象的关联关系。

这个功能是 select标签的 resultType 属性所不具备的,resultType 属性只能完成单表映射。

注意:resultType和resultMap都是处理sql语句返回结果,只能同时存在一个属性。

image-20220219191404133

当表的列名和 pojo 对象的属性名不对应的时候?有几种解决方案

​ 1. 给表的列名起别名

​ 2. 在 mybatis 的全局配置文件中开启 mapUnderscoreToCamelCase (下划线转驼峰命名)

​ 3. 使用 ResultMap 标签

<!--该标签的作用:做结果映射 -->
<resultMap id="getBookByIdResultMap" type="com.atguigu.bean.Book">
    <!-- 主键映射用id标签-->
    <result column="id" property="id"/>
    <!-- 普通字段用result标签映射-->
    <result column="title" property="title"/>
    <!-- 当表的列名和pojo对象的属性名一致的情况下,resultMap能自动完成映射-->
    <result column="img_path" property="imgPath"/>
</resultMap>
<select id="getBookById" parameterType="java.lang.Integer"  resultMap="getBookByIdResultMap">
    select id,title,author,price,sales,stock,img_path from ${name} where title like '${condition}' order by ${orderByField} desc limit 0,1
</select>

2.在映射文件中获取接口各种类型的入参

各种各样的请求参数:

1.普通类型单个参数:#{随便写}

2.普通类型多个参数[两个及以上]:

​ 在每个参数前面加一个@Param(value="") , #{@Param注解value属性值}

3.参数是一个POJO对象:#{pojo对象的属性名}

4.参数是一个Map对象:#{map的key}

5.参数是一个List、Collection:#{list[下标]} | #{collection[下标]}|#{@Param注解value属性值[下标]} @Param****注解不可省略

6.参数是一个数组:#{array[下标]}| |#{@Param注解value属性值[下标]}

7.参数是复杂类型[由上面任意组合而成]:#{@Param注解的value属性值}

Mapper 接口:

public Map<String,Object> getUserMapByUid(Integer uid);
public int deleteByUid(Integer uid);
public int getTotalRecord();
@MapKey("uid")//专门用来指定将表的哪一列作为返回值Map的key
public Map<Integer,User> getUsersMap();
public User getUserByUserNameAndPassword(@Param("username") String username,@Param("username")String password);

Mapper 的 SQL 映射文件:

<select id="getUserMapByUid" resultType="map">
    select uid,username,password,email from user where uid = #{dsdsdsldssdls}
</select>

<select id="getTotalRecord" resultType="_int">
    select count(*) from user
</select>

<delete id="deleteByUid">
    delete from user where uid = #{uid}
</delete>

<select id="getUsersMap" resultType="map">
    select uid,username,password,email from user
</select>

<select id="getUserByUserNameAndPassword" resultType="com.atguigu.bean.User">
    select uid,username,password,email from user where username = #{username} and password = #{arg1}
</select>

3.如何映射接口中各种类型的返参

1.普通数据类型:int、Integer、String、double、Double、boolean

​ 增删改: 只需要在接口处声明返回值类型即可,在SQL映射文件中不需要指定resultType

​ 查询: 需要在接口处声明返回值类型,同时必须在SQL映射文件中指定返回结果类型

2.POJO对象: 需要在接口处声明返回值类型,同时指定resultType为POJO对象全类名

3.POJO对象列表:需要在接口处声明返回值类型,同时指定resultType为POJO对象全类名

4.Map:

​ 返回一行数据:接口处声明Map<String,Object>, 同时指定resultType为Map类型

​ 返回多行数据:接口处声明Map<表的主键类型,POJO对象类型>,同时指定resultType为Map类型,并在接口上使用注解@MapKey("uid")

public interface UserMapper {

    public User getUserByUnameAndPwd(@Param("uname") String uname, @Param("pwd") String pwd);

    public void insertUser(User user);

    public User getUerByMap(Map<String, Object> map);

    public List<User> getUsersByUids(List<Integer> ids);

    public List<User> getUsersByComplex(@Param("map") Map<String, Object> map, @Param("user")User user);

    public int deleteUser(List<Integer> ids);

    public int getTotalRecord();

    @MapKey("id")
    public Map<Integer,User> getUserByUnames(String[] unames);

    @MapKey("uname")
    public Map<String,User> getUserByMap2();
}
<select id="getUserByUnameAndPwd"  resultType="com.atguigu.poji.User">
    select * from t_user where uname = #{uname} and pwd = #{pwd}
</select>


<insert id="insertUser" >
    <selectKey keyColumn="id" keyProperty="id" resultType="java.lang.Integer" order="AFTER">
        select last_insert_id()
    </selectKey>
    insert into t_user values(null,#{uname},#{pwd},#{email},#{role})
</insert>

<select id="getUerByMap"  resultType="com.atguigu.poji.User">
    select * from t_user where uname = #{uname} and pwd = #{pwd}
</select>

<select id="getUsersByUids"  resultType="com.atguigu.poji.User">
    select * from t_user where id in (#{list[0]},#{list[1]},#{list[2]})
</select>

<select id="getUsersByComplex"  resultType="com.atguigu.poji.User">
    select * from t_user where uname = #{map.uname} and pwd = #{user.pwd}
</select>

<delete id="deleteUser" >
    delete from t_user where id in (#{list[0]},#{list[1]},#{list[2]})
</delete>

<select id="getTotalRecord" resultType="int">
    select count(*) from t_user
</select>

<select id="getUserByUnames" resultType="map">
    select * from t_user where uname in (#{array[0]},#{array[1]},#{array[2]})
</select>

<select id="getUserByMap2" resultType="map">
    select * from t_user
</select>

6.基于注解的 SQL 语句

不需要xml文件,不建议使用

@Insert("insert into users(name,age) values(#{name},#{age})")
public void insertT(User user);

@Delete("delete from users where id=#{id}")
public void deleteById(int id);

@Update("update users set name=#{name},age=#{age} where id=#{id}")
public void updateT(User user);

@Select("select * from users where id=#{id}")
public User getUser(int id);

@Select("select * from users")
public List<User> getAllUsers();

7.#{}与${}的区别

区别:

​ #{}:是采用占位符的方式[?],是以预编译的形式,将参数设置到 sql 语句中,相当于PreparedStatement对象,能防止 sql 注入

​ ${}:取出的值直接拼装在 sql 语句中,会有sql注入等安全问题,相当于 Statement 对象;

大多情况下,我们取参数的值都应该去使用#{};

原生 JDBC 不支持使用占位符预编译的一些情况,我们就可以使用${}进行取值来拼接,#{}只是取出参数中的值!

在某些情况下,比如 获取表名、排序字段、有些模糊查询;按照年份分表拆分

select * from ${year}_salary where xxx;[表名不支持预编译]

select * from tbl_employee order by ${f_name} ${order} :排序(字段名、asc|desc)是不支持预编译的!

select * from ${name} where title like '${condition}' order by ${orderByField} desc limit 0,1

​ 模糊查询:name like concat('%',#{value},'%') //name like concat(concat('%', #{name}), '%') **orcle数据库支持

​ name like '%${name}%'

"%"#{value}"%"

8.关联查询:一对多、多对一。。

表与表之间关系:一对一、一对多、多对一、多对多

Mybatis中:

​ x 对一:一对一、多对一 (3种查询方法)

​ x 对多:一对多、多对多 (2种查询方法)

如何查询关联表

1.x对一[三种解决方案]

查询语句

第一种:连缀的方式

<select id="getEmployeeWithDeptByEid" resultMap="EmployeeMap">
    select e.*,d.* from employee e left join dept d on e.did = d.did where e.eid = #{eid}
</select>
<resultMap id="EmployeeMap" type="com.atguigu.poji.Employee">
    <id column="eid" property="eid" />
    <result column="ename" property="ename"/>
    <result column="did" property="dept.did"/>
    <result column="dept_name" property="dept.deptName"/>
</resultMap>

第二种解决方案:使用association标签(用来给引用数据类型属性赋值)

association标签 : 处理对一关联关系。

property标签 : 指定属性名(引用数据类型的)

javaType标签 : 指定属性的类型(引用数据类型的)

<select id="getEmployeeWithDeptByEid" resultMap="EmployeeMap">
    select e.*,d.* from employee e left join dept d on e.did = d.did where e.eid = #{eid}
</select>
<resultMap id="EmployeeMap" type="com.atguigu.poji.Employee">
    <id column="eid" property="eid" />
    <result column="ename" property="ename"/>
    <association property="dept" javaType="com.atguigu.poji.Dept">
        <id column="did" property="did"/>
        <result column="dept_name" property="deptName"/>
    </association>
</resultMap>

第三种解决方案:分步查询

property:指定属性名(引用数据类型的)

select:指定要引用哪个select语句(可以引用外部的sql语句,使用 namespace.id )

column:将查询出来的某一列传递给引用的select语句

<select id="getDeptById" resultType="com.atguigu.poji.Dept">
    select * from dept d where d.did = #{did}
</select>
<resultMap id="EmployeeMap" type="com.atguigu.poji.Employee">
    <id column="eid" property="eid" />
    <result column="ename" property="ename"/>
    <association property="dept" select="com.atguigu.dao.DeptMapper.getDeptById" column="eid"/>
</resultMap>

<select id="getEmployeeWithDeptByEid" resultMap="EmployeeMap">
    SELECT * FROM employee e WHERE e.eid = #{eid}
</select>

针对分步查询,存在懒加载机制

懒加载[按需加载]: 需要的时候就加载,不需要的时候就不加载

​ 多表时,后续没有用到哪个表的信息,其对应的分步的sql语句就不执行

懒加载分为:局部懒加载和全局懒加载

注意:如果全局懒加载和局部懒加载都开启就近原则,听局部懒加载的

​ 局部懒加载:适用于只有少量的 SQL 语句需要懒加载的时候

fetchType:指定局部懒加载策略,值:eager立即加载 lazy:懒加载

<resultMap id="EmployeeMap" type="com.atguigu.poji.Employee">
    <id column="eid" property="eid" />
    <result column="ename" property="ename"/>
    <association fetchType="lazy" property="dept" select="com.atguigu.dao.DeptMapper.getDeptById" column="eid"/>
</resultMap>

​ 全局懒加载:适用于大部分的 SQL 语句都需要懒加载。

​ 在 myatis-config.xml 的全局配置文件中配置:

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>  //开启自动驼峰命名映射
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

2.x对多[二种解决方案]

查询语句

第一种:在 resultMap 标签内部嵌套collection标签(用来给集合类型等对多的属性赋值)

属性:

​ property:指定属性名(集合类型等对多的)

​ ofType:指定数据类型(关联属性集合中元素的数据类型)

<select id="getDeptWhitEmployeeById" resultMap="getDeptWhitEmployeeByIdMap">
    select * from dept t1 left join employee t2 on t1.did = t2.did where t1.did = #{id}
</select>

<resultMap id="getDeptWhitEmployeeByIdMap" type="com.atguigu.poji.Dept">
    <id column="did" property="did"/>
    <result column="dept_name" property="deptName"/>
    <collection property="employees" ofType="com.atguigu.poji.Employee">
        <id column="eid" property="eid"/>
        <result column="ename" property="ename"/>
    </collection>
</resultMap>

第二种:在 resultMap 标签内部嵌套 collection 标签,分步查询(可以设置懒加载,同上)

property:指定属性名(集合类型等对多的)

select:指定要引用哪个select语句(可以引用外部的sql语句,使用 namespace.id 即sql的id )

column:将查询出来的某一列传递给引用的select语句

<select id="getDeptWhitEmployeeById2" resultMap="getDeptWhitEmployeeByIdMap2">
    select did,dept_name from dept t1 where t1.did = #{id}
</select>

<resultMap id="getDeptWhitEmployeeByIdMap2" type="com.atguigu.poji.Dept">
    <id column="did" property="did"/>
    <result column="dept_name" property="deptName"/>
    <collection property="employees" select="com.atguigu.dao.EmployeeMapper.getEmployeeByDid" column="did"/>
</resultMap>
<select id="getEmployeeByDid" resultType="com.atguigu.poji.Employee" >
    SELECT eid,ename,did FROM employee e WHERE e.did = #{did}
</select>

如何创建关联表

一对一表的创建:

第一种:在多对一的基础上,对多的一端的外键,加 unique 唯一约束。

​ 从表的外键列引用主表被参照的列时,外键列加唯一约束

CREATE TABLE wife (
    wid int PRIMARY KEY AUTO_INCREMENT,
    wife_name varchar(30) NOT NULL
);

CREATE TABLE husband (
    hid int PRIMARY KEY AUTO_INCREMENT,
    husband_name varchar(30) NOT NULL,
    wid int UNIQUE,
    FOREIGN KEY (wid) REFERENCES wife (wid)
)

第二种:从表的主键列同时还作为外键列引用主表的主键

CREATE TABLE wife (
    wid int PRIMARY KEY AUTO_INCREMENT,
    wname varchar(30) NOT NULL
);

CREATE TABLE husband (
    hid int PRIMARY KEY,
    husband_name varchar(30) NOT NULL,
    FOREIGN KEY (hid) REFERENCES wife (wid)
)

多对多表的创建:

创建中间表,中间表引用多个外部表的主键作为外键,主键为多表主键的联合主键(任何一列都可以重复,但不能同时重复)

CREATE TABLE teacher (
    tid int PRIMARY KEY AUTO_INCREMENT,
    tname varchar(30) NOT NULL
);

CREATE TABLE student (
    sid int PRIMARY KEY AUTO_INCREMENT,
    sname varchar(30) NOT NULL
);

CREATE TABLE teacher_student (
    tid int,
    sid int,
    PRIMARY KEY (tid, sid),
    FOREIGN KEY (tid) REFERENCES teacher (tid),
    FOREIGN KEY (sid) REFERENCES student (sid)
);

9.动态 SQL

功能:根据复杂的业务条件灵活的拼接成不同的SQL语句

动态 SQL 中涉及到几个标签:

if: 相当于jstl中的<c:if>标签,用于但分支判断

choose (when, otherwise):相当于jstl中的<c:choose><c:when><c:otherwise>用于多分支判断

trim (where, set):trim标签用于修改SQL语句

where标签:代替SQL语句中的where关键字

set标签:代替SQL语句中的set关键字

foreach标签:遍历集合或者数组

传单个值test=。。。只能写value

select *
    from t_travelgroup
    <where>
        <if test="value != null">
            code = #{value} or name like concat("%",#{value},"%") or helpCode = #{value}
        </if>
    </where>

if 标签

提供的参数有啥就获取啥,然后拼接到sql语句中

public List<Book> getBooksByCondition(Map<String,Object> map);
<select id="getBooksByCondition" resultType="com.atguigu.pojo.Book">
    select * from t_book
    where 1 = 1
    <if test="id != null">
       and id = #{id}
    </if>
    <if test="bname != null and bname != ''">
        and bname = #{bname}
    </if>
    <if test="author != null and author != ''">
        and author = #{author}
    </if>
    <if test="price != null and price > 0">
        and price = #{price}
    </if>
</select>

where标签

用来代替where关键字,where标签能将where标签内部所有拼接之后的SQL语句最前面的and|or关键字给去掉

select id="getBooksByCondition" resultType="com.atguigu.pojo.Book">
    select * from t_book
    <where>
        <if test="id != null">
           and id = #{id}
        </if>
        <if test="bname != null and bname != ''">
            and bname = #{bname}
        </if>
        <if test="author != null and author != ''">
            and author = #{author}
        </if>
        <if test="price != null and price > 0">
            and price = #{price}
        </if>
    </where>
</select>

trim 标签

属性:

​ prefix: 表示前缀,具体是指当trim标签内部所有的SQL拼接完毕之后,在最前面加的SQL关键字

​ prefixOverrides:表示前缀覆盖,具体是指使用指定的SQL关键字将trim标签内部所有的SQL拼接完毕之后最前面的SQL关键字去掉

​ suffix: 表示后缀,具体是指当trim标签内部所有的SQL拼接完毕之后,在最后面加的SQL关键字

​ suffixOverrides:表示后缀覆盖,具体是指使用指定的SQL关键字将trim标签内部所有的SQL拼接完毕之后最后面的SQL关键字去掉

<select id="getBooksByCondition" resultType="com.atguigu.pojo.Book">
    select * from t_book
    <trim prefix="where" prefixOverrides="and|or" suffixOverrides="and|or">
        <if test="id != null">
           and id = #{id}
        </if>
        <if test="bname != null and bname != ''">
            and bname = #{bname}
        </if>
        <if test="author != null and author != ''">
            and author = #{author}
        </if>
        <if test="price != null and price > 0">
            and price = #{price}
        </if>
    </trim>
</select>

set 标签

用于代替 sql 语句中的 set 关键字,可以将 set 标签内部的 sql 语句 最前 | 后面 多出来的,[逗号]去掉。

<update id="updateBook">
     update t_book
     <set>
         <if test="bname != null and bname != ''">
             , bname = #{bname}
         </if>
         <if test="author != null and author != ''">
             , author = #{author}
         </if>
         <if test="price != null and price > 0">
             , price = #{price}
         </if>
     </set>
    where id =#{id}
</update>

此处也可以使用 trim 标签解决

<update id="updateBook">
     update t_book
     <trim prefix="set" prefixOverrides="," suffixOverrides=",">
         <if test="bname != null and bname != ''">
              bname = #{bname} ,
         </if>
         <if test="author != null and author != ''">
              author = #{author} ,
         </if>
         <if test="price != null and price > 0">
              price = #{price} ,
         </if>
     </trim>
    where id =#{id}

choose (when, otherwise)标签

用于多分支判断,类似于 java 基础学过的 switch..case 语句。

需求:如果前端携带了书名,就用书名查询,如果携带了作者信息,就用作者信息查,如果携带了价格,就用价格查询,如果这些字段都携带了,就用书名查询,如果都没携带,就查询所有的书籍。

<select id="getBooksByChoose" resultType="com.atguigu.pojo.Book">
    select * from t_book where
    <choose>
        <when test="bname!=null and bname!=''">
            bname=#{bname}
        </when>
        <when test="author != null and author != ''">
            author = #{author}
        </when>
        <when test="price != null and price > 0">
            price = #{price}
        </when>
        <otherwise>
            1=1
        </otherwise>
    </choose>
</select>

foreach标签:遍历集合和数组

collection:表示要遍历的集合

item:指定一个变量接收集合中遍历出来的元素

separator:指定元素与元素之间的分隔符

open:当foreach标签遍历完集合中所有的元素之后,在元素的最前面加的sql字符串

close:当foreach标签遍历完集合中所有的元素之后,在元素的最后面加的sql字符串

<select id="getBookByIds" resultType="com.atguigu.pojo.Book">
    select * from t_book
    where
      id in
        <foreach collection="list" item="id" separator="," open="(" close=")"> 
        	<!--list、array代表列表类型的参数(也可以使用@Param自己指定参数名)-->
            #{id}
        </foreach>
</select>
<select id="getBookByIds" resultType="com.atguigu.pojo.Book">
    select * from t_book
    where
      id in
        <foreach collection="array" item="id" separator="," open="(" close=")">  
        	<!--list、array代表列表类型的参数(也可以使用@Param自己指定参数名)-->
            #{id}
        </foreach>
</select>

批量添加数据|删除

<insert id="batchInsert">
    insert into t_book values
    <foreach collection="list" item="book" separator=",">
        (null,#{book.bname},#{book.bimg},#{book.author},#{book.price},#{book.sales},#{book.bcount},#{book.bstatus})
    </foreach>
</insert>

10.MyBatis 的缓存机制

MyBatis系统中默认定义了两级缓存

一级缓存和二级缓存。

1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。

2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

一级缓存

​ 一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。

​ 本地缓存不能被关闭, 但可以调用 clearCache()来清空本地缓存, 或者改变缓存的作用域.

​ 在mybatis3.1之后, 可以配置本地缓存的作用域,在 mybatis.xml 中配置

image-20220219204653196

一级缓存演示&失效情况

​ 同一次会话期间只要查询过的数据都会保存在当

​ 前SqlSession的一个Map中

​ key:hashCode+查询的SqlId+编写的sql查询语句+参数

​ 一级缓存失效的四种情况

​ 1、不同的SqlSession对应不同的一级缓存

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

​ 3、同一个SqlSession两次查询期间执行了任何一次增删改操作

​ 4、同一个SqlSession两次查询期间手动清空了缓存 —— 执行sqlSession.clearCache()方法

二级缓存

当二级缓存开启后,同一个命名空间(namespace) 所有的操作语句,都影响着一个共同的 cache,也就是二级缓存被多个 SqlSession 共享,

是一个全局的变量

当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

​ 二级缓存(second level cache),全局作用域缓存

​ 基于namespace级别的缓存:一个namespace对应一个二级缓存(即一个XXMapper.xml文件对应一个二级缓存)

二级缓存默认不开启,需要手动配置

​ MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口

二级缓存在 SqlSession 关闭或提交之后才会生效

工作机制:

​ 1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;

​ 2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;

​ 3、sqlSession=EmployeeMapper>查出的Employee对象

​ DepartmentMapper===>查出的Department对象

​ 不同namespace查出的数据会放在自己对应的缓存中(底层是map)

​ 效果:数据会从二级缓存中获取

查出的数据都会被默认先放在一级缓存中。

只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中

​ 二级缓存使用步骤

​ 1、全局配置文件中开启二级缓存

 ~~~xml
~~~

​ 2、需要使用二级缓存的XXMapper.xml映射文件处使用cache标签配置缓存

  ~~~xml
~~~

​ 3、注意:POJO需要实现Serializable接口

缓存有关设置

1、全局setting的cacheEnable:

配置二级缓存的开关。一级缓存一直是打开的。

2、select标签的useCache属性:

配置这个select是否使用二级缓存。一级缓存一直是使用的

3、sql标签的flushCache属性:

增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。

查询默认flushCache=false。

4、sqlSession.clearCache():

只是用来清除一级缓存。

5、当在某一个作用域 (一级缓存Session/二级缓存Namespaces) 进行了 C/U/D 操作后,默认该作用域下所有select 中的缓存将被clear

image-20220219205346500

第三方缓存整合

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。

MyBatis定义了Cache接口方便我们进行自定义扩展。

步骤:

1、导入ehcache包,以及整合包,日志包

​ ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar

​ slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar

2、编写ehcache.xml配置文件

3、配置cache标签

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

参照缓存:若想在命名空间中共享相同的缓存配置和实例。

可以使用 cache-ref 元素来引用另外一个缓存

image-20220219205430287

11.SSM整合

12.MyBatis 逆向工程

MyBatis 逆向工程:是指 MyBatis 可以根据表直接生成表对应的 pojo 类、Mapper 接口、 SQL 映射文件。

MyBatis 逆向工程使用 MyBatis Generator(MyBatis 生成器)完成。

MyBatis Generator 简称为 MBG

MyBatis 逆向工程只是针对单表的,对于有关联关系的表生成不了连表操作。

MyBatis Generator 具体使用:

1.原生ssm工程中使用

第一步:创建动态 web 工程,导入 jar 包

​ mybatis-generate-core、mysql、mybatis

第二步:准备 MyBatis Generator 配置文件:generatorConfig.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>

    <!-- 有Example查询条件内容 -->
    <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/bookdb"
                userId="root"
                password="ok">
        </jdbcConnection>

        <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
            NUMERIC 类型解析为java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- targetProject:生成Entity类的路径 -->
        <javaModelGenerator targetProject=".\src" targetPackage="com.atguigu.bean">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!-- targetProject:XXXMapper.xml映射文件生成的路径 -->
        <sqlMapGenerator targetProject=".\src" targetPackage="com.atguigu.dao">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>

        <!-- targetPackage:Mapper接口生成的位置 -->
        <javaClientGenerator type="XMLMAPPER" targetProject=".\src" targetPackage="com.atguigu.dao">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>



        <!-- 数据库表名字和我们的entity类对应的映射指定 -->
  
        <table tableName="t_book" domainObjectName="Book"/>
        <table tableName="t_cart_detail" domainObjectName="Cart"/>
        <table tableName="t_order" domainObjectName="Order"/>
        <table tableName="t_order_detail" domainObjectName="OrderDetail"/>
        <table tableName="t_user" domainObjectName="User"/>

    </context>
</generatorConfiguration>

第三步:使用 java 代码进行操作即可

public static void main(String[] args) throws Exception {
    List<String> warnings = new ArrayList<String>();
    boolean overwrite = true;
    File configFile = new File("generatorConfig.xml");
    ConfigurationParser cp = new ConfigurationParser(warnings);
    Configuration config = cp.parseConfiguration(configFile);
    DefaultShellCallback callback = new DefaultShellCallback(overwrite);
    MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
    myBatisGenerator.generate(null);
}

2.maven工程中使用

1. maven的pom文件中添加插件

<!-- 控制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>
                <!-- 数据库连接池 -->
                <dependency>
                    <groupId>com.mchange</groupId>
                    <artifactId>c3p0</artifactId>
                    <version>0.9.2</version>
                </dependency>
                <!-- MySQL驱动 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>5.1.8</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>


2. 添加配置文件

<?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(奢华尊享版)
     -->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis-example"
                        userId="root"
                        password="root">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.atguigu.entity" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="mappers"  targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mapper"  targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_emp" domainObjectName="Employee"/>
        <table tableName="t_customer" domainObjectName="Customer"/>
        <table tableName="t_order" domainObjectName="Order"/>
    </context>
</generatorConfiguration>


13.源码解析

四大对象及其调用的重要方法
Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed) --simple(默认) resuer batch 3种(详见下)
StatementHandler (prepare, parameterize, batch, update, query)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)

Executor:执行器,由它来调度StatementHandler、并由StatementHandler来调度ParameterHandler、ResultSetHandler等来执行对应的SQL。

StatementHandler:使用数据库的Statemet(PreparedStatement)执行操作

ParameterHandler:处理SQL参数;比如查询的where条件、和insert、update的字段值的占位符绑定。

ResultSetHandler:处理结果集ResultSet的封装返回。将数据库字段的值赋给实体类对象的成员变量。

疑问:MyBatis是对JDBC的封装。那SqlSession是不是就是对Connection的封装,SqlSessionFactory是否就是对DrimverMangaer的封装,Mapper是否就是对Statement的封装。错了,都错了。

SqlSession是一个接口,这里返回的是其实现类DefaultSqlSession的一个实例。

其中有一个Executor类型的成员变量。其实进行数据库操作时SqlSession就是一个门面,真正干活的却是Executor。

Executor 执行目标方法时创建了StatementHandler(详见下图)

StatementHandler(预编译时调用了)>ParameterHandler(对参数安装TypeHandle规则进行了转化)=>parameterObject(具体的参数)

RoutingStatementHandler:根据stateType类型生成 SimpleStatementHandler / PreparedStatementHandler(默认) / CallableStatementHandler

1.源码解析总结

@Test
public void test01() throws IOException {
    // 1、获取sqlSessionFactory对象,详见下
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    // 2、获取sqlSession对象,详见下
    SqlSession openSession = sqlSessionFactory.openSession();
    try {
        // 3、获取接口的实现类对象,详见下
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        //4、执行增删改查方法
        Employee employee = mapper.getEmpById(1);
        System.out.println(mapper.getClass());
        System.out.println(employee);
    } finally {
        openSession.close();
    }

}
1、获取sqlSessionFactory对象:
	解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;
	注意:【MappedStatement】:代表一个增删改查的详细信息

2、获取sqlSession对象
		返回一个DefaultSQlSession对象,包含Executor和Configuration;
		这一步会创建Executor对象;

3、获取接口的代理对象(MapperProxy)
		getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象
		代理对象里面包含了,DefaultSqlSession(Executor)
4、执行增删改查方法

总结:
1、根据配置文件(全局,sql映射)初始化出Configuration对象
2、创建一个DefaultSqlSession对象,
他里面包含Configuration以及
Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
3、DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;
4、MapperProxy里面有(DefaultSqlSession);
5、执行增删改查方法:
1)、调用DefaultSqlSession的增删改查(Executor);
2)、会创建一个StatementHandler对象。
(同时也会创建出ParameterHandler和ResultSetHandler)
3)、调用StatementHandler预编译参数以及设置参数值;
使用ParameterHandler来给sql设置参数
4)、调用StatementHandler的增删改查方法;
5)、ResultSetHandler封装结果
注意:
四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler);

2.具体细节

0.图示

image-20220222063444182

1、根据配置文件创建SQLSessionFactory

image-20220220151804386

image-20220220151816404

parser:解析 XML (DOM解析 Document对象)

image-20220220151846618

Configuration:保存了所有配置文件的详细信息

image-20220220151943420

MappedStatement:(全局Configuration中的一个重要属性)

​ 表示一个增删改查标签

image-20220220151920128

MapperRegistry:(全局Configuration中的一个重要属性)

· 内部维护一个map:knownMappers.put(type, new MapperProxyFactory<T>(type)) (type为mapper接口的Class对象)

​ MapperProxyFactory通过JDK动态代理生成Maper接口的代理对象

image-20220220152413526

2、返回SqlSession的实现类DefaultSqlSession对象。

他里面包含了Executor和Configuration(Executor会在这一步被创建)

​ ExecutorType:SIMPLE(默认)、REUSE、BATCH ---根据不用的ExecutorType来new 不同的Executor对象

SimpleExecutor是一种常规执行器,每次执行都会创建一个statement,用完后关闭。
ReuseExecutor是可重用执行器,将statement存入map中,操作map中的statement而不会重复创建statement。
BatchExecutor是批处理型执行器,doUpdate预处理存储过程或批处理操作,doQuery提交并执行过程。

​ 二级缓存:

​ 判断是否二级缓存(cacheEnabled默认为false),来将Executor包装一层

​ 本质还是调用原来的Executor对象,只是在查询前先去查询缓存(第一次查询结果会放入缓存中),缓存中没有才会去DB查询

image-20220220151329782

3、getMapper返回接口的代理对象,包含了SqlSession对象

image-20220220151703764

image-20220220151637516

4、查询流程

image-20220220151614087

image-20220220153354798

查询时先走缓存

​ 缓存中保存的key:方法id(全类名.方法名) + sql + 参数xxx

image-20220220153413515

14.MyBatis-插件开发

插件原理

在四大对象创建的时候

1、每个创建出来的对象不是直接返回的,而是 基于 interceptorChain.pluginAll(parameterHandler);

2、获取到所有的Interceptor(拦截器)(插件需要实现的接口);调用interceptor.plugin(target);返回target包装后的对象

3、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)

​ 我们的插件可以为四大对象创建出代理对象;

​ 代理对象就可以拦截到四大对象的每一个执行;

插件会产生目标对象的代理对象

image-20220220221938429

多个插件就会产生多层代理

​ 后面的插件会把前面的包起来再生成代理对象,而执行interceptor方法时是外层方法先执行,所以后面的插件定义的方法先执行

image-20220220222038846

即:创建动态代理的时候,是按照插件配置顺序创建层层代理对象。执行目标方法的之后,按照逆向顺序执行

image-20220220222546689

插件编写步骤

1、编写Interceptor的实现类

2、使用@Intercepts注解完成插件签名

3、将写好的插件注册到全局配置文件的plugins标签中

代码示例:

import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

/**
 * 完成插件签名:
 *		告诉MyBatis当前插件用来拦截哪个对象(4大对象)的哪个方法,args用来区分重载的方法
 */
@Intercepts(
		{
			@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
		})
public class MyFirstPlugin implements Interceptor{

	/**
	 * intercept:拦截:
	 * 		拦截目标对象的目标方法的执行;
	 */
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
		//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询3号员工
		Object target = invocation.getTarget();
		System.out.println("当前拦截到的对象:"+target);//target为StatementHandler对象(@Signature中type表明了)
		//拿到:StatementHandler==>ParameterHandler===>parameterObject
		//拿到target的元数据
		MetaObject metaObject = SystemMetaObject.forObject(target);
		Object value = metaObject.getValue("parameterHandler.parameterObject");
		System.out.println("sql语句用的参数是:"+value);
		//修改完sql语句要用的参数
		metaObject.setValue("parameterHandler.parameterObject", 11);
		//执行目标方法
		Object proceed = invocation.proceed();
		//返回执行后的返回值
		return proceed;
	}

	/**
	 * plugin:
	 * 		包装目标对象的:包装:为目标对象创建一个代理对象
	 */
	@Override
	public Object plugin(Object target) {
		// TODO Auto-generated method stub
		//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
		System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);
		Object wrap = Plugin.wrap(target, this);
		//返回为当前target创建的动态代理
		return wrap;
	}

	/**
	 * setProperties:
	 * 		将插件注册时 的property属性设置进来
	 */
	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
		System.out.println("插件配置的信息:"+properties);
	}

}
<configuration>

	<!--plugins:注册插件  -->
	<plugins>
		<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
			<property name="username" value="root"/>
			<property name="password" value="123456"/>
		</plugin>
		<plugin interceptor="com.atguigu.mybatis.dao.MySecondPlugin"></plugin>
	</plugins>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
				<property name="username" value="root" />
				<property name="password" value="123456" />
			</dataSource>
		</environment>
	</environments>
	<!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
	<mappers>
		<mapper resource="EmployeeMapper.xml" />
	</mappers>
</configuration>

PageHelper插件进行分页

MyBatis 分页插件 PageHelper 的具体使用:

第一步:导入 PageHelper 的 jar 包

第二步:在 mybatis 全局配置文件中配置分页插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="xxx1" value="xxx1"/> <!-- 此处缩写参数可在插件的setProperties() 方法中获取-->
        <property name="xxx2" value="xxx2"/>
    </plugin>
</plugins>

第三步:在 service 层写分页代码

@Test
public void test01() throws IOException {
    // 1、获取sqlSessionFactory对象
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    // 2、获取sqlSession对象
    SqlSession openSession = sqlSessionFactory.openSession();
    try {
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Page<Object> page = PageHelper.startPage(5, 1);

        List<Employee> emps = mapper.getEmps();
        //传入要连续显示多少页
        PageInfo<Employee> info = new PageInfo<>(emps, 5);
        for (Employee employee : emps) {
            System.out.println(employee);
        }
        /*System.out.println("当前页码:"+page.getPageNum());
        System.out.println("总记录数:"+page.getTotal());
        System.out.println("每页的记录数:"+page.getPageSize());
        System.out.println("总页码:"+page.getPages());*/
        ///xxx
        System.out.println("当前页码:"+info.getPageNum());
        System.out.println("总记录数:"+info.getTotal());
        System.out.println("每页的记录数:"+info.getPageSize());
        System.out.println("总页码:"+info.getPages());
        System.out.println("是否第一页:"+info.isIsFirstPage());
        System.out.println("连续显示的页码:");
        int[] nums = info.getNavigatepageNums();
        for (int i = 0; i < nums.length; i++) {
            System.out.println(nums[i]);
        }            
        //xxxx
    } finally {
        openSession.close();
    }
}
@Autowired
private BooksMapper booksMapper;

public PageInfo<Books> getPage(Integer pageNum){
    BooksExample booksExample = new BooksExample();
    //1.在查询列表之前使用PageHelper.startPage(pageNum, pageSize);
    PageHelper.startPage(pageNum,5);
    //2.查询列表
    List<Books> books = booksMapper.selectByExample(booksExample);
    //3.使用PageInfo对象构造器封装查询的列表
    PageInfo<Books> pageInfo = new PageInfo<>(books,5);
    return pageInfo;
}

15.Others

自动生成主键

要求:接口参数必须是 pojo 对象(向数据表中插入数据后,可通过原对象获取主键值)

第一种解决方案:适用于数据库支持表的自增主键:mysql、sqlserver(oracle不支持)

//useGeneratedKeys:表示使用数据表的自增主键
//keyProperty:指定使用pojo对象的哪个属性接收主键值
<insert id="insert" useGeneratedKeys="true" keyProperty="idXx" parameterType="x"> <!-- parameterType=""可省略不写,但规范化还是写为好-->
    insert into user(idXx,username,password,email) values(null,#{username},#{password},#{email})<!-- 此处id可以省略 -->
</insert>

​ 不是DB生成的自增主键,而是设置了uuid的函数作为主键默认值,须通过以下方式获取主键值

​ eg:pg库 sys_guid()

//useGeneratedKeys:表示使用数据表的自增主键
//keyProperty:指定使用pojo对象的哪个属性接收主键值
<insert id="insert" useGeneratedKeys="true" keyProperty="uid" keyColumn="id_xx" parameterType="x"> <!-- parameterType=""可省略不写,但规范化还是写为好-->
    insert into user(uid,username,password,email) values(null,#{username},#{password},#{email})<!-- 此处id可以省略 -->
</insert>

第二种解决方案:适用所有的数据库

​ keyColumn:表示查询表的哪一列作为主键,指定表的列名

​ keyProperty:指定使用pojo对象的哪个属性接收主键值

​ resultType:返回结果的类型

​ order:表示当前selectKey标签中的SQL语句在当前insert标签的insert语句的执行顺序。

​ last_insert_id()函数:获取最后插入数据的主键

​ 其返回值不是基于整个数据库的插入语句,而是基于单个连接客户端之间所执行的insert语句最近一条,

​ 而且客户端之间是不会影响的,它是连接级别的函数,只对当前用户的连接有效--所有是线程安全的

注:使用select last_insert_id()时要注意,当一次插入多条记录时,只是获得第一次插入的id值,务必注意!

<insert id="insert">
    <selectKey keyColumn="uid" keyProperty="uid" resultType="java.lang.Integer" order="AFTER">
         select last_insert_id()
    </selectKey>
    insert into user(uid,username,password,email) values(null,#{usernmae},#{password},#{email})
</insert>

测试代码

@Test
public void test04() throws Exception {
    。。。
    BookMapper mapper = sqlSession.getMapper(BookMapper.class);
    Book book = new Book(0,"PP","ZZ","WW",12.9,233,555,1);
    mapper.insertBook(book);
    System.out.println(book.getId());
    sqlSession.close();
}

批量操作

一般不会在全局配置文件mybatis-config.xml中配置ExecutorType,而是针对sqlSession级别来设置

@Test
public void testBatch() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    
    //可以执行批量操作的sqlSession
    SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    long start = System.currentTimeMillis();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        for (int i = 0; i < 10000; i++) {
            mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
        }
        openSession.commit();
        long end = System.currentTimeMillis();
        //批量:(预编译sql一次==>设置参数===>10000次===>执行(1次))
        //Parameters: 616c1(String), b(String), 1(String)==>10000/4598次 (4598是根据控台打印所得)
        //非批量:(每次都要 预编译sql=设置参数=执行)==》10000次    10200
        System.out.println("执行时长:"+(end-start));
    }finally{
        openSession.close();
    }
    
}

与Spring整合中,额外的配置一个可以专门用来执行批量操作的sqlSession

​ 使用时,自动注入 SqlSession对象,调用它的getMapper(xxxMapper.class) 返回一个xxxMapper对象,此时调用此对象的crud方法即为批量操作

<!--配置一个可以进行批量执行的sqlSession  -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
    <constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>

注意:
1、批量操作是在session.commit()以后才发送sql语句给数 据库进行执行的

2、如果想让其提前执行,以方便后续可能的查询操作获取数据,可以使用sqlSession.flushStatements()方 法,让其直接冲刷到数据库进行执行

存储过程

oracle分页:

​ 步骤:1.子查询先获取行号rownum 2.对已有的行号rownum进行操作 (详见SQL笔记)

​ rownum 是在已产生数据的基础上伪生成的编号(已有数据会自动生成rownum 这个列)

​ 所以 使用rownum 必须在已有数据的基础上,因此Oracle分页才加入了多个子查询

--查询前3条:类似Sqlserver中的TOP 3
select *
  from (select * from table_Name order by active_count desc)
 where rownum <= 3;

--查询第2至第3条记录:
select *
  from (select t.*, rownum as no
        from (select * from table_Name order by active_count desc) t)
 where no between 2 and 3;
 
 --在TOP3条记录的基础上查询第2至第3条记录:
 select *
  from (select t.*, rownum as no
        from (select * from table_Name order by active_count desc) t  where rownum <= 3 )
 where no between 2 and 3;
 
 --查询第2条以后的记录:
 select *
  from (select t.*, rownum as no
        from (select * from table_Name order by active_count desc) t)
 where no >=2
select rownum ,e.* from emp e;

--查询员工信息的前5条数据 第一页数据

select rownum r,e.* from emp e where rownum <=5;
select * from (select rownum r,e.* from emp e where rownum <=5) t where r>0;

--查询员工信息的6-10条数据 第二页数据

select rownum r,e.* from emp e where rownum <=10;
select rownum,t.* from (select rownum r,e.* from emp e where rownum <=10) t where r>5;

--查询员工信息的11-15条数据 第三页数据

select rownum r,e. * from emp e where rownum<=15;
select * from (select rownum r,e. * from emp e where rownum<=15) t where r>10;

--分页规律总结:每页显示m条数据,查询第n页数据*

select * from (select rownum r,e. * from 要分页的表 e where rownum<=m*n) t where r>m*n-m ;

--分页查询员工信息按照工资排序后的 6-10条数据 第二页数据

select * from (select rownum r,t.* from (select * from emp order by sal) t where rownum<=10 ) where r>5

代码示例

1.创建 封装分页查询数据的java类

/**
 * 封装分页查询数据
 */
@Data
public class OraclePage {
	private int start;
	private int end;
	private int count;
	private List<Employee> emps;

}

2.在oracle中创建存储过程procedure hello_test

create or replace procedure 
hello_test(p_start in int, 
           p_end in int, 
           p_count out int, 
           ref_cur out sys_refcursor) AS
BEGIN
	select count(*) into p_count from emp;
	open ref_cur for
		select * from (
            select e.*,rownum as rm from emp e where rownum<=p_end
        ) 
		where rm >= p_start;
END hello_test;

3.配置Oracle数据库环境

​ 全局配置文件

<environments default="dev_oracle">
    <environment id="dev_oracle">
        <transactionManager type="JDBC" />
        <dataSource type="POOLED">
            <property name="driver" value="${orcl.driver}" />
            <property name="url" value="${orcl.url}" />
            <property name="username" value="${orcl.username}" />
            <property name="password" value="${orcl.password}" />
        </dataSource>
    </environment>

    <environment id="dev_mysql">
        <transactionManager type="JDBC" />
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
        </dataSource>
    </environment>
</environments>

<databaseIdProvider type="DB_VENDOR">
    <property name="MySQL" value="mysql"/>
    <property name="Oracle" value="oracle"/>
    <property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>

3.xxxMapper接口中定义分页查询方法

public interface EmployeeMapper {
	public void getPageByProcedure(OraclePage page);
}

4.写sql调用存储过程

<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
	<!-- public void getPageByProcedure(); 
	1、使用select标签定义调用存储过程
	2、statementType="CALLABLE":表示要调用存储过程
	3、{call procedure_name(params)}
	-->
	<select id="getPageByProcedure" statementType="CALLABLE" databaseId="oracle">
		{call hello_test(
			#{ start, mode = IN, jdbcType = INTEGER },
			#{ end, mode = IN, jdbcType = INTEGER },
			#{ count, mode = OUT, jdbcTyp = INTEGER },
			#{emps, mode = OUT, jdbcType = CURSOR, javaType = ResultSet, resultMap = PageEmp }
        <!-- 从游标中取数封装给 OraclePage对象的List<Employee> emps属性,
				列名与Employee不一致需要定义resultMap表明如何映射
			jdbcType:mybatis中的枚举类  javaType:JDBC原生的结果集类,都是通过他来映射结果 -->
		)}
	</select>
	<resultMap type="com.atguigu.mybatis.bean.Employee" id="PageEmp">
		<id column="EMPLOYEE_ID" property="id"/>
		<result column="LAST_NAME" property="email"/>
		<result column="EMAIL" property="email"/>
	</resultMap>
</mapper>

5.调用分页方法

@Test
public void testProcedure() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        OraclePage page = new OraclePage();
        page.setStart(1);
        page.setEnd(5);
        mapper.getPageByProcedure(page);
        //存储过程需要两个入参(p_start,p_end),在OraclePage中设置好传入方法,
        //查询结果的两个返参(p_count,ref_cur)会自动设置给OraclePage

        System.out.println("总记录数:"+page.getCount());
        System.out.println("查出的数据:"+page.getEmps().size());
        System.out.println("查出的数据:"+page.getEmps());
    }finally{
        openSession.close();
    }	
}

自定义TypeHandler处理枚举

1、指定全局配置EnumOrdinalTypeHandler(Mybatis默认是使用EnumTypeHandler,存、取时都是用name属性)

​ 在全局配置文件中指定,处理某个类时,使用指定TypeHandler处理

image-20220222072436051

2.测试参数位置设置自定义TypeHandler

​ 1.插入时,对单个字段处理 2.在全局配置文件中指明:对某个类型存、取时,都使用指定的TypeHandler

image-20220222073134769

​ 自定义TypeHandler

public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus> {

    /**
	 * 定义当前数据如何保存到数据库中
	 */
    @Override
    public void setParameter(PreparedStatement ps, int i, EmpStatus parameter,
                             JdbcType jdbcType) throws SQLException {
        // TODO Auto-generated method stub
        System.out.println("要保存的状态码:"+parameter.getCode());
        ps.setString(i, parameter.getCode().toString());
    }

    @Override
    public EmpStatus getResult(ResultSet rs, String columnName)
        throws SQLException {
        // TODO Auto-generated method stub
        //需要根据从数据库中拿到的枚举的状态码返回一个枚举对象
        int code = rs.getInt(columnName);
        System.out.println("从数据库中获取的状态码:"+code);
        EmpStatus status = EmpStatus.getEmpStatusByCode(code);
        return status;
    }

    @Override
    public EmpStatus getResult(ResultSet rs, int columnIndex)
        throws SQLException {
        // TODO Auto-generated method stub
        int code = rs.getInt(columnIndex);
        System.out.println("从数据库中获取的状态码:"+code);
        EmpStatus status = EmpStatus.getEmpStatusByCode(code);
        return status;
    }

    @Override
    public EmpStatus getResult(CallableStatement cs, int columnIndex)
        throws SQLException {
        // TODO Auto-generated method stub
        int code = cs.getInt(columnIndex);
        System.out.println("从数据库中获取的状态码:"+code);
        EmpStatus status = EmpStatus.getEmpStatusByCode(code);
        return status;
    }

}

补充说明:

1.自定义类型处理器可以处理任意类型的字段对DB存、取时按照自定义的规则进行转换

2.可以在处理某个字段的时候告诉MyBatis用什么类型处理器(单个SQL级别):可以通过在xml的sql语句中增加对应配置来实现

注意:如果在参数位置修改TypeHandler,应该保证保存数据和查询数据用的TypeHandler是一样的

​ 插入时,使用自定义的TypeHandler处理Enum

image-20220222071939164

#{empStatus,typeHandler=xxxx}

​ 查询时,使用自定义的TypeHandler处理Enum

image-20220222072111803

<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmp">
    <id column="id" property="id"/>
    <result column="empStatus" property="empStatus" typeHandler=""/>
</resultMap>

16.Mybatis-plus

1.枚举的优雅使用

实战中学到的知识

1.根据list(id的集合)内容order后将结果映射为map<id,多字段>

方法使用示例:

//此处Map<String, Object>中k:String指字段名,v:Object为每行数据指导字段的值,所以只能使用Object封装
List<Map<String, Object>> maps = baseMapper.queryAttrValuesMappingSkuId(skuIds);

mapper接口

List<Map<String, Object>> queryAttrValuesMappingSkuId(@Param("skuIds") List<Long> skuIds);

mapper.xml映射文件

<select id="queryAttrValuesMappingSkuId" resultType="map">
    SELECT GROUP_CONCAT(attr_value ORDER BY attr_id) attr_values,sku_id
    FROM pms_sku_attr_value
    WHERE sku_id
    IN
    <foreach collection="skuIds" item="skuId" separator=","  open="(" close=")">
        #{skuId}
    </foreach>
    GROUP BY sku_id;
</select>

函数:

group_concat([DISTINCT] 要连接的字段 [Order BY ASC/DESC 排序字段] [Separator '分隔符'])

​ GROUP_CONCAT(... ORDER BY ...) ... GROUP BY..;

SELECT GROUP_CONCAT(attr_value ORDER BY attr_id) attr_values,sku_id FROM pms_sku_attr_value WHERE sku_id IN (1,2,3) GROUP BY sku_id;--默认分隔符为 ,  可以指定分隔符

image-20210916004450882 ==》 image-20210916004301184

SELECT GROUP_CONCAT(attr_value ORDER BY attr_id separator '|') attr_values,sku_id FROM pms_sku_attr_value WHERE sku_id IN (1,2,3) GROUP BY sku_id;--指定分隔符

image-20220218071146514

2.自定义类型处理器存储jsonb格式(pg库)

mybatis-plus

@TableField(typeHandler = xxx.class) -->在指定字段上加自定义类型处理器

public class XxxListTypeHandler extends AbstractJsonTypeHandler<List<XxxBo>>{
    
    @Override
    protected List<XxxBo> parse(String json){ //解析从pg库获取的jsonb格式的数据,反序列化成List<XxxBo>
        return JSON.parseArray(json,XxxBo.class);
    }
    
    @Override
    protected String toJson(List<XxxBo>obj){// 将List<XxxBo>序列化成json格式,存入指定的pg库jsonb格式字段
        return JSON.toJsoNString(obj);
    }
}

3.关于mysql的主键设置问题

在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一,单机递增),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究竟有什么坏处?

mysql自增主键的优缺点

https://cloud.tencent.com/developer/article/1694709?from=15425

4.同步数据任务

1.分页查询同步

2.通过api分批次同步

/**
 *	全量 数据
 */
@Override
@Transactional(rollbackFor = Exception.class)
public void syncDataToXx(){
    // 若有脏数据需要重复执行同步数据逻辑,先清表
    xxDAO.truncateData();
    // 获取数据源全部id集合
    List<String> yyIds = yyDAO.selectIds();
    // 根据id集合,分批插入数据
    Lists.partition(yyIds,500).forEach(subIds ->{
        List<yyDo> yyDos = yyDAO.selectBatchIds(subIds);
        List<xxDo> xxDos = yyDos.stream()
            .map(yyDo ->{
                XxDo xxDo = new XxDo();
                copyProperties(yyDo,xxDo); )
            .collect(Collectors.toList());
        xxDAO.saveBatch(xxDos);
      );
 }

5.模糊查询拼接'%'

​ mysql写法:

select * from like concat('%',#{value},'%');

​ pgsql写法:(使用 || 来拼接,若直接使用 concat函数,会报错 :无法确定参数的类型)

select * from like '%'||#{value}||'%';
--或者 select * from like concat('%',#{value}::varchar,'%'); -- like concat('%',#{value}::text,'%')

6.手写分页

1.Steam流+lambda实现分页

page.setRecords(
	dataList.stream
    		.skip((long)(Math.max(1,currentPage)-1) * pageSize)//跳过前面页数的数据
			.limit(pageSize)//只展示当前页数据
    		.collect(Collectors.toList())
);

image-20220504154318177

优化方案:

如果怕dataList太大,撑爆内存,可先查出全部id的集合,把id集合分页,然后在map里根据当前id查出一条数据

page.setRecords(
	idList.stream
    		.skip((long)(Math.max(1,currentPage)-1) * pageSize)//跳过前面页数的数据
			.limit(pageSize)//只展示当前页数据
    		.map(id-> xxxDao.getByid(id))
    		.collect(Collectors.toList())
);

2.java原生代码

总页数 = (数据总条数 + 每页条数 - 1)/ 每页条数

分页sql:(以下两条sql结果相同)

select * from 表 limit (page-1)*sizePerPage, sizePerPage;
select * from 表 limit sizePerPage offset (page-1)*sizePerPage;

posted on 2022-03-08 21:46  freyhe  阅读(86)  评论(0编辑  收藏  举报