Java_MyBatis

Java开发加强

1. 目标

掌握主流的web开发框架,能够使用SSM(springMVC SpringBoot Mybatis)开发web应用

在学习web开发之前,有一些需要清楚的前置知识。

2. Java的用武之地

常见的软件架构体系主要分为两种:CS架构、BS架构

java主要用于开发BS架构的应用

2.1. CS架构

  • client客户端 server服务端
  • 需要在本地安装客户端程序才能访问服务端
  • QQ 、杀毒软件、网游

2.2. BS架构

  • 即浏览器/服务器(Browser/Server);
  • 本地只要有浏览器即可
  • 用浏览器打开的任何应用:淘宝、优酷、哔哩哔哩、论坛、博客、地图…

2.3. BS与CS架构的区别

  • 性能方面
    • CS:能充分发挥客户端PC的处理能力,大部分数据处理由客户端完成,处理能力强,速度快,用户体验好,但是对客户端电脑的配置要求高,客户群体受限,使用成本高。
    • BS:所有数据处理由服务器完成,客户端能安装浏览器即可,客户端要求低。
  • 个性化
    • CS:支持丰富的自定义设置,可以充分满足客户端个性化要求(换肤、声音显示设置、按键设置…)
    • BS:支持部分个性化定制功能,不如BS架构丰富
  • 更新问题
    • CS:服务端更新,客户端不更新无法使用
    • BS:只需要服务器端更新,客户端刷新浏览器即可
  • 开发成本
    • CS:不同的系统需要开发一套不同的客户端
    • BS:只需要开发一个版本,客户端能安装浏览器即可
  • 设备依赖性
    • CS:其他没有安装客户端的电脑无法使用;重装系统需要重装客户端
    • BS:客户端能安装浏览器即可

小结:

java主要用于开发BS架构的应用,客户端只需要有浏览器即可访问服务端

3. BS架构通信问题

在这里插入图片描述

在这里插入图片描述

问题:

  • 为什么输入网址能访问到天猫服务,而没有访问其他服务器(京东/苏宁…)
  • 浏览器和服务器是如何通信的
    • 浏览器如何告诉服务器要获取那些数据
    • 服务器怎么知道浏览器要要获取哪些数据
    • 服务器如何将数据响应给浏览器

3.1. IP地址和端口

因特网上的每台计算机和其它设备都规定了一个唯一的地址,叫做“IP地址,但是这个地址是一串数字,不方便记忆,为了方便记忆,互联网提供了域名。一个IP地址可以绑定一个或多个域名。

www.baidu.com 180.101.49.11

找到这台计算机(服务器)之后,但是这台计算机上启动了N多个服务(百度地图、百度翻译、百度贴吧…),怎么访问到百度搜索的程序呢?

通过端口就能确定要访问计算机上的哪个程序了。默认端口80

3.2. HTTP协议

HTTP(hypertext transport protocol),即超文本传输协议。这个协议详细规定了浏览器和万维网服务器之间互相通信的规则。

HTTP就是一个通信规则,通信规则规定了客户端发送给服务器的内容格式,也规定了服务器发送给客户端的内容格式。其实我们要学习的就是这个两个格式!客户端发送给服务器的格式叫“请求协议”;服务器发送给客户端的格式叫“响应协议”。

在这里插入图片描述

3.3. 请求协议与响应协议

  • 请求协议的格式如下:

    • 请求首行
    • 请求头
    • 请求空行
    • 请求体
  • 响应协议

    • 响应首行

    • 响应头

    • 响应空行

    • 响应体

  • POST请求

  • GET请求

3.5. 小结:

1. 浏览器通过IP地址:端口 访问到应用程序
  1. 浏览器和服务器之间通过HTTP协议进行通信

4. 服务器架构

前面我们已经清楚了BS架构的特点,接下来我们看看后端开发的应用程序的结构特点

4.1. 三层架构的意义

三层架构的目的着重点是解耦,也就是项目开发强调的“高内聚,低耦合”。不同的层级负责不同的工作。好比厨房后厨的分工:切菜、炒菜、传菜

在这里插入图片描述

  • 用户下单:
  1. 表现层(controller)负责接收客户端请求:购买商品ID,购买数量
  2. controller 调用业务层(service)处理下单业务,
  3. service 通过持久层(dao)获取数据库中的数据:判断是否黑名单用户、判断商品是否存在、是否限制购买数量、判断库存…下单(扣减库存、生成订单…).
  4. 数据持久层:完成对数据库中数据的读写操作,查询用户、查询商品、查询库存、修改库存、生成订单,将数据提供给service完成逻辑处理。
  5. service层完成下单功能,将下单结果返回给controller。
  6. controller再将业务层处理的下单结果返回给客户端(下单成功/失败)

4.2. MVC

三层架构是对整个项目的结构划分,MVC是针对表现层再度进行了划分,MVC分别表示 Model-View-Controller,最典型的MVC就是JSP + servlet + javabean的模式

Model(模型):模型对象负责封装数据(User、Student…)

View(视图):视图用于对模型数据进行展示,例如可以把一组数据展示为柱状图或者饼状图。

Controller(控制器):控制器负责从视图读取用户输入,将对应的模型数据转交给视图展示,起到控制协调作用

  • 各层的框架

SpringMVC:表现层框架,用于接收客户端请求,响应业务逻辑层的处理结果给客户端

Spring/SpringBoot:业务逻辑层框架,用于整合其他框架,提供非常丰富的业务处理功能,事务处理、定时任务、权限校验…

MyBatis:数据访问层(持久层)框架,负责和数据库打交道,从数据库中查询数据、写入数据。

4.3. 总结:

  1. java主要用来开发BS架构的web应用。
  2. 客户端与服务器端通过HTTP协议进行通信。
  3. 服务端应用程序为了解耦,分为三层架构设计:表现层、业务层、持久层。
  4. 其中表现层又进行了细分:模型、视图、控制器。
  5. 我们要掌握每一层对应框架的应用。

MyBatis

官网地址:https://mybatis.org/mybatis-3/zh/getting-started.html

目标:

掌握使用XML配置以及注解两种方式,能够完成各种简单已经复杂的需求,将数据写入到数据库,以及从数据将数据从数据库中查询出来。并掌握相关延伸功能:分页插件、逆向工程、通用mapper。

1. 环境准备

1.1. 创建表和类

  • 创建数据库mybatis ,执行【资料】- 【mybatis.sql】完成表的创建和数据插入。

  • 新建包com.czxy.mybatis,将【资料】- 【model】文件夹复制到com.czxy.mybatis包下

1.2. 创建maven项目

1.3. 导入依赖

pom.xml

<!-- mybatis核心依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>

<!-- mysql连接依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
</dependency>

<!-- junit测试 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</dependency>

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

1.4. 添加日志配置

将【资料】- 【log4j.properties】复制到项目的resource目录下

2. MyBatis 入门案例(xml)

使用xml配置的方式查询出user表中的数据

  1. 配置数据库连接

  2. 设置mybatis核心配置文件

  3. 编写mybatis的sql映射文件

  4. 测试

2.1. 数据库连接

jdbc.properties

注意mybatis版本>=6.0使用如下驱动,如果<6.0使用com.mysql.jdbc.Driver
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&&serverTimezone=Hongkong&useSSL=false
username=root
password=root

2.2. MyBatis核心配置

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>
    <!--引入jdbc属性配置-->
    <properties resource="jdbc.properties"></properties>
    <!--mybatis的核心环境配置-->
    <environments default="development">
        <environment id="development">
            <!--
             在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
             JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施
             MANAGED – 这个配置几乎没做什么
             -->
            <transactionManager type="JDBC"/>
            <!--type可选值:UNPOOLED 不使用连接池
                          POOLED使用连接池
                          JNDI 从配置好的JNDI数据源获取连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

2.3. mapper.xml

在resource目录下新建mapper目录,存放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="test">

    <!--
    id:sql片段的唯一标识,同一个mapper文件中不能重复
    parameterType:参数类型
    resultType:返回值类型
    -->
    <select id="selectOne" parameterType="int" resultType="com.czxy.mybatis.model.User">
        SELECT
        uid,
        username,
        birthday,
        phone,
        sex,
        address
        FROM `user`
        WHERE uid = #{aa}
    </select>

</mapper>

2.4. 测试

测试类:MyBatisTest.java

public class MyBatisTest {

    @Test
    public void selectOne() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
        //可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //通过 namespace.id的方式确定要执行的SQL片段
        User user = sqlSession.selectOne("test.selectOne",10);
        System.out.println(user);
        //用完后必须管理连接
        sqlSession.close();

    }

}

总结:

  • mybatis开发过程总结
  1. 编写mybatis-config.xml
  2. 编写mapper.xml
  3. 编程通过配置文件创建SqlSessionFactory
  4. 通过SqlSessionFactory获取SqlSession
  5. 通过SqlSession操作数据库:
    1. 如果执行添加、更新、删除需要调用SqlSession.commit()
  6. SqlSesion使用完成要关闭

3. XML配置

MyBatis 的配置文件(mybatis-config.xml)包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

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

3.1. 配置文件详解

<?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>
    <!--引入jdbc属性配置-->
    <properties resource="jdbc.properties"></properties>
    <settings>
        <!--开启驼峰规则映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--设置别名,在mapper.xml中指定类型时可以简化书写-->
    <!--
        resultType="com.czxy.mybatis.model.User"
        简化为
        resultType="user"
    -->
    <typeAliases>
        <!--给某一个类取别名-->
        <typeAlias type="com.czxy.mybatis.model.User" alias="user"></typeAlias>
        <!--扫描某一个包下的类,指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean-->
        <!--这样有10个或者100个类不用一个个取指定别名-->
        <package name="com.czxy.mybatis.model"/>
    </typeAliases>
    <!--mybatis的核心环境配置-->
    <environments default="development">
        <environment id="development">
            <!--
             transactionManager:配置事务管理器
             在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
             JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施
             MANAGED – 这个配置几乎没做什么
             -->
            <transactionManager type="JDBC"/>
            <!--
            dataSource: 配置数据源
            type可选值:UNPOOLED 不使用连接池
                          POOLED使用连接池
                          JNDI 从配置好的JNDI数据源获取连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--映射器-->
    <mappers>
        <!--加载映射文件的位置-->
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

附件 - MyBatis配置列表

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true | falsefalse
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。true | falsefalse (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled是否允许单个语句返回多结果集(需要数据库驱动支持)。true | falsetrue
useColumnLabel使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。true | falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。true | falseFalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 SqlSessionException)NONE, WARNING, FAILINGNONE
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。SIMPLE REUSE BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定数据库驱动等待数据库响应的秒数。任意正整数未设置 (null)
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。任意正整数未设置 (null)
defaultResultSetType指定语句默认的滚动策略。(新增于 3.5.2)FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置)未设置 (null)
safeRowBoundsEnabled是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。true | falseFalse
safeResultHandlerEnabled是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。true | falseTrue
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true | falseFalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。SESSION | STATEMENTSESSION
jdbcTypeForNull当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。OTHER
lazyLoadTriggerMethods指定对象的哪些方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成使用的默认脚本语言。一个类型别名或全限定类名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5)一个类型别名或全限定类名。org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。true | falsefalse
returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)true | falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
proxyFactory指定 Mybatis 创建可延迟加载对象所用到的代理工具。CGLIB | JAVASSISTJAVASSIST (MyBatis 3.3 以上)
vfsImpl指定 VFS 的实现自定义 VFS 的实现的类全限定名,以逗号分隔。未设置
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)true | falsetrue
configurationFactory指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)一个类型别名或完全限定类名。未设置
shrinkWhitespacesInSqlRemoves extra whitespace characters from the SQL. Note that this also affects literal strings in SQL. (Since 3.5.5)true | falsefalse

附件 - 默认别名列表

MyBatis为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

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

4. XML - 映射文件

MyBatis 的真正强大在于它的语句映射,MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

SQL 映射文件只有很少的几个顶级元素(只列出常用元素):

  • select – 映射查询语句。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • sql – 可被其它语句引用的可重用语句块。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。

4.1. select

查询语句是 MyBatis 中最常用的元素之一,能把数据存到数据库中价值并不大,还要能重新取出来才有用,多数应用也都是查询比修改要频繁。前面我们写了一个比较简单的select

<select id="selectOne" parameterType="int" resultType="com.czxy.mybatis.model.User">
    SELECT
    uid,
    username,
    birthday,
    phone,
    sex,
    address
    FROM `user`
    WHERE uid = #{aa}
</select>

这个语句名为selectOne,接受一个 int(或 Integer)类型的参数,并返回一个 User类型的对象.

4.1.1. 参数符号

注意参数符号:#{aa}

这就告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,

这样做更安全,更迅速,通常也是首选做法,因为参数会被双引号 “” 包裹,避免SQL注入,上述语句对应的SQL:

SELECT
    uid,
    username,
    birthday,
    phone,
    sex,
    address
FROM `user`
WHERE uid = "10"

参数符号还有另外一种方式: ${id}

有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:

ORDER BY ${columnName}
-- mapper
SELECT 
  uid,
  username,
  birthday,
  phone,
  sex,
  address
FROM `user`
ORDER BY ${columnName} DESC

-- 对应的SQL
SELECT 
  uid,
  username,
  birthday,
  phone,
  sex,
  address
FROM `user`
ORDER BY uid DESC -- 这里的列名就没有被双引号包裹

4.1.2. 参数类型

parameterType 用于明确参数类型,可以指定这条语句的参数类的全限定名或别名。

这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)

  • 参数只有一个
    • int / java.lang.Integer
    • string
    • float
    • double
    • date
  • 参数有多个
    • parameterType=“map” / java.util.HashMap
    • user / com.czxy.mybatis.model.User
  • 使用map和pojo类型传参
<!--参数是map类型-->
<select id="listUser" parameterType="map" resultType="user">
    SELECT
    uid,
    username,
    birthday,
    phone,
    sex,
    address
    FROM `user`
    WHERE sex = #{sex}
    AND phone like #{phone}
</select>

@Test
public void listUser() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
    SqlSession sqlSession = sqlSessionFactory.openSession();

    //准备查询参数
    //1. pojo类型
    //User user = new User();
    //user.setPhone("%"+888+"%");
    //user.setSex("1");

    //2.map类型参数,注意key必须和占位符名称一致#{key},否则获取不到参数
    HashMap<String, Object> map = new HashMap<>();
    map.put("phone","%"+888+"%");
    map.put("sex","1");

    //通过 namespace.id的方式确定要执行的SQL片段
    //List<User> list = sqlSession.selectList("test.listUser", user);
    List<User> list = sqlSession.selectList("test.listUser", map);

    for (User u : list) {
        System.out.println(u);
    }
    //用完后必须管理连接
    sqlSession.close();

}

4.1.3. 结果映射
- resultType
  1. 返回结果的数据类型。通常 MyBatis 可以推断出来,但是为了更加准确,建议写上。
  2. 返回一条或者多条数据,结果类型不变
    • 返回一条User类型数据和返回多条User类型数据,resultType结果都是resultType=“user”
  3. resultType和resultMap必须有一个属性被指定
<select id="selectUser" parameterType="int" resultType="map">
    SELECT
    uid,
    username,
    birthday,
    phone,
    sex,
    address
    FROM `user`
    WHERE uid = #{uid}
</select>

返回结果:

{birthday=1998-10-16, uid=10, address=北京海淀, phone=13999999999, sex=1, username=张三}

- resultMap

当查询结果有多个列,并且列名和字段名称不一致时,mybatis无法将结果封装到model对象中,这是需要通过resultMap绑定列和属性关系

<!--resultMap结果映射:绑定列和属性关系-->
<resultMap id="baseResultMap" type="user">
    <id property="uid" column="uid" javaType="int" jdbcType="INTEGER"></id>
    <result property="username" column="username"></result>
    <result property="sex" column="sex"></result>
    <result property="birthday" column="birthday"></result>
    <result property="phone" column="phone"></result>
    <result property="address" column="address"></result>
</resultMap>

<!--参数是map类型, 返回结果类型是resultMap-->
<select id="listUser2" parameterType="map" resultMap="baseResultMap">
    SELECT
    uid,
    username,
    birthday,
    phone,
    sex,
    address
    FROM `user`
    WHERE sex = #{sex}
    AND phone like #{phone}
</select>

测试:

@Test
public void listUser2() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
    SqlSession sqlSession = sqlSessionFactory.openSession();

    //map类型参数,注意key必须和占位符名称一致#{key},否则获取不到参数
    HashMap<String, Object> map = new HashMap<>();
    map.put("phone","%"+6+"%");
    map.put("sex","1");

    //通过 namespace.id的方式确定要执行的SQL片段
    List<User> list = sqlSession.selectList("test.listUser2", map);

    for (User u : list) {
        System.out.println(u);
    }
    //用完后必须管理连接
    sqlSession.close();

}

附件 - 支持的 JDBC 类型

为了以后可能的使用场景,MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型。

BITFLOATCHARTIMESTAMPOTHERUNDEFINED
TINYINTREALVARCHARBINARYBLOBNVARCHAR
SMALLINTDOUBLELONGVARCHARVARBINARYCLOBNCHAR
INTEGERNUMERICDATELONGVARBINARYBOOLEANNCLOB
BIGINTDECIMALTIMENULLCURSORARRAY

4.2. insert

  • UserMapper.xml
<!--parameterType可以省略,mybatis会自动推导参数类型-->
<insert id="insertUser">
    INSERT INTO `user`
    (uid,username,birthday,phone,sex,address)
    VALUES(NULL,#{username},#{birthday},#{phone},#{sex},#{address})
</insert>

  • 测试:MyBatisTest.java
@Test
public void testInsert() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
    SqlSession sqlSession = sqlSessionFactory.openSession();

    //准备数据
    User user= new User();
    user.setUsername("曹操");
    user.setBirthday(new Date());
    user.setAddress("安徽亳州");
    user.setPhone("13666666666");
    user.setSex("1");

    //通过 namespace.id的方式确定要执行的SQL片段
    sqlSession.insert("test.insertUser",user);

    //提交事务
    sqlSession.commit();
    //用完后必须管理连接
    sqlSession.close();
}

4.2.1. 返回生成的主键ID

在插入语句里面有一些额外的属性和子元素用来处理主键的生成,如果你的数据库支持自动生成主键的字段,那么可以进行如下设置 :

  • useGeneratedKeys=”true”,
  • keyProperty =“目标属性”

这样做的好处是可以返回自动生成的主键。

例如,如果上面的 user表已经在 uid 列上使用了自增主键,那么语句可以修改为:

  • UserMapper.xml
<!--返回自动生成的主键-->
<insert id="insertUserAndReturnID" useGeneratedKeys="true" keyProperty="uid">
    INSERT INTO `user`
    (uid,username,birthday,phone,sex,address)
    VALUES(NULL,#{username},#{birthday},#{phone},#{sex},#{address})
</insert>

  • 测试:MyBatisTest.java
  @Test
public void testInsert() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
    SqlSession sqlSession = sqlSessionFactory.openSession();

    //准备数据
    User user= new User();
    user.setUsername("曹操");
    user.setBirthday(new Date());
    user.setAddress("安徽亳州");
    user.setPhone("13666666666");
    user.setSex("1");

    //通过 namespace.id的方式确定要执行的SQL片段
    //sqlSession.insert("test.insertUser",user);
    sqlSession.insert("test.insertUserAndReturnID",user);
    System.out.println("---------- " +user.getUid()+" -----------");
    //提交事务
    sqlSession.commit();
    //用完后必须管理连接
    sqlSession.close();
}

应用场景:商品相关操作的日志记录

管理员新上架商品:张三(10)上架华为P40 Pro(1001),对应日志如下:

日志ID人员ID商品ID操作描述时间
1101001create上架商品HUAWEI P40 Pro2020-07-19 18:32:17

4.3. update

UserMapper.xml

<update id="updateUser" >
    UPDATE `user`
    SET phone = #{phone}
    WHERE uid = #{uid}
</update>

  • 测试:MyBatisTest.java
@Test
public void updateUser() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
    SqlSession sqlSession = sqlSessionFactory.openSession();

    User user = new User();
    user.setUid(32);
    user.setPhone("13612345678");
    //通过 namespace.id的方式确定要执行的SQL片段
    sqlSession.update("test.updateUser",user);

    //提交事务
    sqlSession.commit();
    //用完后必须管理连接
    sqlSession.close();

}

4.4. delete

  • UserMapper.xml
<delete id="deleteUser" >
    DELETE FROM `user` WHERE uid = #{uid}
</delete>

  • 测试:MyBatisTest.java
@Test
public void deleteUser() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //使用map传递参数,KEY必须和占位符#{key}名称一致,否则获取不到参数
    HashMap<String, Integer> map = new HashMap<>();
    map.put("uid",2);
    //通过 namespace.id的方式确定要执行的SQL片段
    sqlSession.update("test.deleteUser",map);

    //提交事务
    sqlSession.commit();
    //用完后必须管理连接
    sqlSession.close();

}

4.5. SQL

前面我们在写SQL的时候可以发现,出现大量重复的SQL片段,例如查询的时候,列名在个SQL都有书写

SELECT
    uid,
    username,
    birthday,
    phone,
    sex,
    address
FROM `user`
WHERE uid = #{aa}

这时候可以用SQL元素来定义可重用的 SQL 代码片段,以便在其它语句中使用。比如:

<sql id="baseColunm">
    uid,username,birthday,phone,sex,address
</sql>

<!--参数是map类型, 返回结果类型是resultMap-->
<select id="listUser2" parameterType="map" resultMap="baseResultMap">
    SELECT
    	<include refid="baseColunm"></include>
    FROM `user`
    WHERE sex = #{sex}
    AND phone like #{phone}
</select>

5. mapper代理

前面使用mybatis 的JavaAPI完成的相关CRUD操作,下面看看开发中,在DAO层如何使用

  1. 编写mapper接口

  2. mapper.xml遵循如下约定:

    2.1. mapper.xml中namespace指定为mapper接口的全限定名

    2.2. mapper.xml中statement的id就是DAO接口中方法名

    2.3. mapper.xml中statement的parameterType和DAO接口中方法输入参数类型一致

    2.4. mapper.xml中statement的resultType和DAO接口中方法返回值类型一致.

  3. mybatis-config.xml保持不变

5.1. 添加工具类

创建sqlSessionFactory和获取sqlSession的方式是固定的,sqlSessionFactory只需要创建一次即可,因此使用工具类MyBatisUtils来封装相关操作,简化书写,后续SSM整合之后,这些对象的创建就交给spring容器管理了,不需要我们自己管理了。

public class MyBatisUtils {

    private static SqlSessionFactory sqlSessionFactory;
    /*
     * 创建本地线程变量,为每一个线程独立管理一个session对象 每一个线程只有且仅有单独且唯一的一个session对象
     * 使用ThreadLocal对session进行管理,可以保证线程安全,避免多实例同时调用同一个session对象
     */
    private static ThreadLocal<SqlSession> threadlocal = new ThreadLocal<SqlSession>();

    // 创建sessionFactory对象,因为整个应用程序只需要一个实例对象,故用静态代码块
    static {
        try {
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 新建session会话,并把session放在线程变量中
     */
    private static void newSession() {
        // 打开一个session会话
        SqlSession session = sqlSessionFactory.openSession();
        SqlSession sqlSession = threadlocal.get();
        // 将session会话保存在本线程变量中
        threadlocal.set(session);
    }

    /**
     * 返回session对象
     * @return session
     */
    public static SqlSession getSession(){
        //优先从线程变量中取session对象
        SqlSession session = threadlocal.get();
        //如果线程变量中的session为null,
        if(session==null){
            //新建session会话,并把session放在线程变量中
            newSession();
            //再次从线程变量中取session对象
            session = threadlocal.get();
        }
        return session;
    }

    /**
     * 关闭session对象,并从线程变量中删除
     */
    public static void closeSession(){
        //读取出线程变量中session对象
        SqlSession session = threadlocal.get();
        //如果session对象不为空,关闭sessoin对象,并清空线程变量
        if(session!=null){
            //关闭资源
            session.close();
            //从threadlocal中移除session
            threadlocal.set(null);
        }
    }

    /**
     * 提交并关闭资源
     */
    public static void commitAndclose() {

        //获取连接
        SqlSession openSession = getSession();
        //非空判断
        if(openSession!=null) {
            //提交
            openSession.commit();
            //关闭资源
            openSession.close();
            //从threadlocal中移除session
            threadlocal.remove();
        }
    }

}

5.2. 编写mapper接口

5.3.修改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必须是接口全限定名-->
<mapper namespace="com.czxy.mybatis.mapper.UserMapper">

    <!--参数类型必须和接口方法参数类型一致,可以省略不写-->
    <!--返回值类型必须和接口方法参数类型一致-->
    <select id="listUser" parameterType="map" resultType="user">
        SELECT
          uid,
          username,
          birthday,
          phone,
          sex,
          address
        FROM `user`
        WHERE sex = #{sex}
        AND phone like #{phone}
    </select>
</mapper>

5.4. 测试

public class MybatisDaoTest {

    @Test
    public void testMapper(){
        //使用工具类获取sqlSession
        SqlSession sqlSession = MyBatisUtils.getSession();

        //查询参数
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("sex","1");
        map.put("phone","%"+6+"%");

        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.listUser(map);

        for (User user : users) {
            System.out.println(user);
        }

        //关闭会话
        MyBatisUtils.closeSession();
    }
}

6. 高级查询

6.1. 模糊查询

  • 需求:查询手机号包含“888”的用户信息
  • mapper接口:UserMapper.java
public interface UserMapper {

    public List<User> listUser(Map<String,Object> map);
    //模糊查询
    public List<User> selectByPhone(String username);
}

  • UserMapper.xml
<sql id="baseColumn">
    uid,username,birthday,phone,sex,address
</sql>

<!--模糊查询-->
<select id="selectByPhone" resultType="user">
    select <include refid="baseColumn"></include>
    from `user`
    where username like #{name}
</select>

  • 测试
/**
     * 模糊查询
     */
@Test
public void selectByPhone(){
    //使用工具类获取sqlSession
    SqlSession sqlSession = MyBatisUtils.getSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    String name="%张%";
    List<User> users =mapper.selectByPhone(name);

    for (User user : users) {
        System.out.println(user);
    }

    //关闭会话
    MyBatisUtils.closeSession();
}

6.2. 分组查询

需求: 统计男女各多少人

  • mapper接口
public interface UserMapper {
	//...
    //分组查询
    public List<Map<String,Integer>> selectCount(String columnName);
}

  • UserMapper.xml
<!--分组查询,注意取值使用的是${}-->
<select id="selectCount" resultType="map">
    select sex,count(sex) num from `user` group by ${column_name }
</select>

  • 测试
/**
  * 分组查询
  */
@Test
public void selectCount(){
    //使用工具类获取sqlSession
    SqlSession sqlSession = MyBatisUtils.getSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<Map<String, Integer>> listMap = (List<Map<String, Integer>>) mapper.selectCount("sex");
    //便利查询结果
    for (Map<String, Integer> map : listMap) {
        System.out.println(map);
    }
    //关闭会话
    MyBatisUtils.closeSession();
}

7. 动态SQL

7.1. 遇到的问题

客户端查询的信息不确定,因此查询条件不确定,需要动态判断

以前的写法:

  • mapper接口
//多条件查询
public List<User> selectByNameAndBirthday(User user);

  • mapper.xml
<!--多条件查询-->
<select id="selectByNameAndBirthday" resultType="user">
    select <include refid="baseColumn"></include>
    from `user`
    where username like #{username}
    AND birthday = #{birthday}
</select>

  • 测试
  /**
     * 多条件查询
     */
@Test
public void selectByNameOrPhone() throws ParseException {
    //使用工具类获取sqlSession
    SqlSession sqlSession = MyBatisUtils.getSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //模拟接收到的前台参数
    String username = "张";
    Date birthday = new SimpleDateFormat("yyyy-MM-dd")
        .parse("1998-12-25");

    User user = new User();
    user.setUsername("%"+username+"%");
    //user.setBirthday(birthday);

    //根据条件查询
    List<User> users = mapper.selectByNameAndBirthday(user);
    for (User u : users) {
        System.out.println(u);
    }

    //关闭会话
    MyBatisUtils.closeSession();
}

  • 问题:

    当没有输入全部查询条件时,反而查询不到结果,可以从日志的SQL语句中找到原因

==>  Preparing: 
select uid,username,birthday,phone,sex,address from `user` where username like ? AND birthday = ?
[DEBUG] 2020-07-19 23:01:26,937 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137)
==> Parameters: %%(String), null

没有传入参数时,参数值为null,对应SQL如下:

select uid,username,birthday,phone,sex,address from `user` where username like " %张%" AND birthday = null

AND birthday = null 肯定查询不到结果,这时就需要动态判断参数是否有值,动态拼接SQL,下面我们就来学习学习MyBatis提供的动态SQL功能。

7.2. 动态SQL

动态 SQL 是 MyBatis 的强大功能之一,可以完成各种复杂的SQL,动态SQL 相关元素如下:

  • if
  • where
  • choose(when otherwise)
  • foreach
  • set(了解)
  • trim(了解)
7.2.1. if

if:对传入参数的属性值进行判断,结果为true则拼接SQL片段,否则不拼接

修改前面根据用户名和手机号查询的SQL片段

  • mapper.xml
<!--动态SQL - if -->
<select id="selectByNameAndBirthday2" resultType="user">
    select <include refid="baseColumn"></include>
    from `user`
    where
    <if test="username != null and username != ''">
        username like #{username}
    </if>
    <if test="birthday != null">
        AND birthday = #{birthday}
    </if>

</select>

这里使用if判断,如果用户输入了username、birthday,则该属性值!=null,拼接对应的查询SQL,否则不拼接

  • 注意

if判断中,条件使用 and 、or拼接,不能使用 & | AND OR

非字符串类型不要进行空串比较,pro != ‘’,因为会先将pro强制转为字符串

  • mapper接口
public List<User> selectByNameAndBirthday2(User user);

  • 测试
/**
     * 动态sql - if
     */
@Test
public void selectByNameOrPhone2() throws ParseException {
    //使用工具类获取sqlSession
    SqlSession sqlSession = MyBatisUtils.getSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //模拟接收到的前台参数
    String username = "张";
    Date birthday = new SimpleDateFormat("yyyy-MM-dd")
        .parse("1998-12-25");

    User user = new User();
    //user.setUsername("%"+username+"%");
    user.setBirthday(birthday);

    //根据条件查询
    List<User> users = mapper.selectByNameAndBirthday2(user);
    for (User u : users) {
        System.out.println(u);
    }

    //关闭会话
    MyBatisUtils.closeSession();
}

测试:输入username 、birthday 结果:正常

测试:输入username 结果:正常

测试:输入birthday 结果:异常,对应SQL

SELECT uid,username,birthday,phone,sex,address FROM `user` WHERE AND birthday = ?

测试:不输入参数 结果 : 异常,对应SQL

SELECT uid,username,birthday,phone,sex,address FROM `user` WHERE

  • 问题
    • 有where条件时,加上where关键字,并自动判断要不要加and
    • 希望没有条件时,不要where关键字
7.2.2. where

使用where元素解决上述问题

<!--动态SQL - if where -->
<select id="selectByNameAndBirthday2" resultType="user">
    select <include refid="baseColumn"></include>
    from `user`
    <where>
        <if test="username != null and username != ''">
            username like #{username}
        </if>
        <if test="birthday != null">
            AND birthday = #{birthday}
        </if>
    </where>
</select>

7.2.3. choose

choose相当于java中的switch

  • 需求
    • 根据性别查询,注意问题,用户输入的是“男”“女”,数据库中国存储的是1和2表示男女
  • mapper接口
//动态sql - choose
public List<User> dynamicSQLChoose(String sex);

  • mapper.xml
<select id="dynamicSQLChoose" resultType="user">
    select
    <include refid="baseColumn"></include>
    from `user`
    <where>
        <choose>
            <when test='sex != null and sex == ""'>
                and sex = '1'
            </when>
            <when test='sex != null and sex == ""'>
                and sex = '2'
            </when>
            <otherwise>
                and sex is not null
            </otherwise>
        </choose>
    </where>
</select>

  • 这里必须注意:当比较的值只有一个字符时,mybatis会把值当成char处理,转成数值,汉字是无法直接转成数值的,因此这里要用双引号,表示是字符串
<when test='sex != null and sex == ""'>

  • 测试
/**
  * 动态SQL choose
  */
@Test
public void dynamicSQLChoose(){
    SqlSession sqlSession = MyBatisUtils.getSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> list = mapper.dynamicSQLChoose("女");
    for (User user : list) {
        System.out.println(user);
    }

    MyBatisUtils.closeSession();
}

7.2.4. foreach

类似于java中for循环,主要用于in语句的条件查询

需求:根据多个ID进行查询

  • UserMapper.xml
<!--
SELECT uid,username,birthday,sex,address
FROM `user`
WHERE uid IN (1,10,12,16)
collection: 遍历的集合类型 arrar -> 数值 list -> list  collection -> set
item ; 变量名,可以自定义,表示遍历的每一项的值
open: 开始内容
separator: 分隔符
close: 结束内容
-->
<select id="dynamicSQLForeach" resultType="user">
    select uid,username,birthday,sex,address
      from `user`
      <if test="collection != null">
        <where>
             <foreach collection="collection" item="item" open="uid IN (" separator="," close=")">
                  #{item}
             </foreach>
        </where>
      </if>
</select>


  • mapper接口
public List<User> dynamicSQLForeach(Set<Integer> ids);

  • 测试
@Test
public void dynamicSQLForeach(){
    //获取连接
    SqlSession sqlSession = MyBatisUtils.getSession();
    //获取mapper代理
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    //查询条件
    //int[] ids = {1,10,12,16};
    //ArrayList<Integer> list = new ArrayList<>();
    //list.add(1);
    //list.add(10);
    //list.add(12);
    //list.add(16);
    HashSet<Integer> set = new HashSet<>();
    set.add(1);
    set.add(10);
    set.add(12);
    //调用接口方法
    //List<User> users = mapper.dynamicSQLForeach(ids);
    //List<User> users = mapper.dynamicSQLForeach(list);

    List<User> users = mapper.dynamicSQLForeach(set);
    //List<User> users = mapper.dynamicSQLForeach(null);
    //遍历查询结果
    for (User u : users) {
        System.out.println(u);
    }
    
    //关闭会话
    MyBatisUtils.closeSession();
}

©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页