MyBatis-2

MyBatis 应用开发

ORM 即对象关系映射,通过这种方式可以实现以操作对象的方式操作关系型数据库。

优势:隐藏了数据访问细节,提供了通过对象模型构造关系数据库结构的可能

缺点:映射和关联管理牺牲了执行性能,并不能完全的屏蔽掉数据库层的设计,复杂查询还是不够方便

MyBatis 概述

MyBatis 是一个优秀的持久层框架,它对 jdbc 的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建 connection、创建 statement、手动设置参数、结果集检索等 jdbc繁杂的过程代码。Mybatis 可以通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中的 sql 进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射成 java

对象并返回。

MyBatis 适用于性能要求高,需求多变的项目,例如互联网项目。以封装少、高性能、可优化、维护简易等优点成为了目前 Java 移动互联网网站服务的首选持久框架,它特别适合分布式和大数据网络数据库的编程

开发总结

mybatis-config.xml 是 mybatis 全局配置文件,只有一个,名称不固定的,主要 mapper.xml,mapper.xml 中配置 sql 语句

mapper.xml 是以 statement 为单位进行配置。把一个 sql 称为一个 statement,satatement 中配置 sql 语句、parameterType 输入参数类型,完成输入映射;resultType 输出结果类型,完成输出映射。

还提供了 parameterMap 配置输入参数类型。过期了,不推荐使用了。

还提供 resultMap 配置输出结果类型,完成输出映射。

核心配置文件

核心配置文件名称推荐使用 mybatis-config.xml,实际上没有严格要求

环境配置

要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

<?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>
    <!-- 最核心的配置就是运行环境的配置和注册映射元文件-->
    <!-- 在具体的应用中可以配置多个运行环境,例如开发环境、测试环境和生成环境等,默认使用哪个配
置是通过 default 定义的-->
    <environments default="yan">
        <!-- 在每个环境配置中主要定义两方面的配置信息,事务管理器和数据源 -->
        <environment id="yan">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///test?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

映射文件生效的前提是:必须在核心配置文件中进行注册

运行时常量配置

setting 是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为

在执行查询时输出所执行的 sql 语句和其它相关的执行日志信息

logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。对应的常量配置值有 SLF4J | LOG4J |LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

选用最简单的日志实现 log4j,但是仅仅用于开发阶段,一般没有特殊需求的情况下,在提交产品时会关闭。

如果运维人员需要进行数据埋点处理,则需要运维人员自行处理添加

1、添加依赖 log4j

2、添加 log4j 所需要的配置文件,配置文件名称为 log4j.properties,位置位于 resources 目录根下,如果配置文件名称变动或者位置变动,则失效

log4j.rootLogger=DEBUG,console
log4j.appender.console=org.apache.log4j.ConsoleAppender 控制台(console)
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n

3、在 mybatis 的核心配置文件中设置打开日志输出

4、执行查询操作,则可以在控制台上查看所执行的 sql 语句

属性配置

如果由核心配置文件负责管理数据库连接相关信息,则认为核心配置文件责任过重。所以有人引入 properties文件达到分离配置的目的

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///test?serverTimezone=UTC
# username=root
# password=123456

然后在核心配置文件中引用 properties 文件

<properties resource="jdbc.properties">
     <property name="username" value="root"/>
     <property name="password" value="123456"/>
</properties>

可以在其它配置位置通过${key}的方式引用 properties 文件配置值或者标签配置值

如果 properties 文件方式的配置和配置文件中使用标签配置的方式冲突时,properties 文件配置优先

类型别名

为了简化类型名称配置,所以 MyBatis 提供了一套别名系统,例如类型全名为 com.ma.entity.UserBean 可以定义别名为 User,则在映射元文件中如果需要使用 UserBean 类型时使用 User 即可。也就是类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

<typeAliases>
     <typeAlias alias="Author 别名" type="domain.blog.Author 全名"/>针对特定的类型指定别名
</typeAliases>

配置方法 2:针对每个类型定义一个别名,过于繁琐,所以指定包名称,由 MyBatis 自动扫描定义别名

<typeAliases>
    <package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。比如 domain.blog.AuthorBean 的别名为 authorBean

配置方法 3:使用注解的方式定义类型别名

@Alias(“Abc”) 定义 Author 类的别名为 Abc

public class Author

需要配合配置方法 2 一起使用

MyBatis 针对常见的 Java 类型内建的类型别名,不需要人为再次定义。例如真实配置可以简写为

别名对应映射的类型:_byte byte、_long long、_short short、_int int、_integer

int、_double double、_float float、_boolean boolean、string String、byte Byte、long Long、short Short、int Integer、integer Integer、double Double、float Float、boolean Boolean、date Date、decimal BigDecimal、bigdecimal BigDecimal、object Object、map Map、hashmap HashMap、list List、arraylist

ArrayList、collection Collection、iterator Iterator

映射器

配置方法 1:使用相对于类路径的资源引用

<mappers>
    <mapper resource="mapper/AuthorMapper.xml"/>
</mappers>

配置方法 2:将包内的映射器接口实现全部注册为映射器,避免一个一个的进行配置注册

<mappers>
     <package name="org.mybatis.builder"/>
</mappers>

配置方法 3:使用映射器接口实现类的完全限定类名

<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>

映射元文件

CRUD 标签常见属性:

id 命名空间中的唯一标识

parameterType 传入 SQL 语句的参数类型

resultMap 就是 SQL 语句返回值的类型映射

selectOne 用于查询单条记录,不能用于查询多条记录,否则异常:

selectList 用于查询多条记录,可以用于查询单条记录的。

结果映射

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作

<resultMap id="BaseResultMap 
标识符,在一个 namespace 中不允许重复" type="com.yan.entity.User 目标实体类型">
    <id column="id" jdbcType="BIGINT" property="id" /> id 用于定义主键和标识属性之间的对应关系,
column 是列名称,property 是对应的属性名称,jdbcType 用于指定对应的数据类型,其中的常量取值的名称
来源于 Types 类中定义的常量
    <result column="username" jdbcType="VARCHAR" property="username" />
 result用于定义非主键类型的列和属性之间的对应关系
    <result column="password" jdbcType="VARCHAR" property="password" />
    <result column="birth" jdbcType="DATE" property="birth" />
    <result column="sex" jdbcType="BOOLEAN" property="sex" />
</resultMap>

注意:resultMap 标签中的特殊属性 autoMapping 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。在列名称和属性名称一致时可以使用 autoMapping=true 进行自动映射,不需要逐列的进行配置

Id 和 Result 的属性

property映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。

column 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。

javaType 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。

jdbcType 是 JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。

typeHandler 定义默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。

sql 代码块

<sql id="Base_Column_List">id, username, password, birth, sex</sql>

后面使用 sql 代码块的方法为

<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
  select
  <include refid="Base_Column_List" /> 
引用上面定义的 sql 代码块,其中 refid 对应<sql>配置的 id 值,相当于将<sql>标签体拷贝到这个位置
  from tb_users where id = #{id,jdbcType=BIGINT}
</select>

实际上在具体的语法中还允许在 sql 代码块中使用参数,不建议使用

增删改操作

返回值类型为 int,用于表示受影响行数

<delete id="deleteByPrimaryKey 当前 sql 语句对应标识符" parameterType="java.lang.Long 参数类型">
  delete from tb_users where id = #{id,jdbcType=BIGINT}
</delete>
<insert id="insert" parameterType="com.yan.entity.User">
  insert into tb_users (id, username, password, birth, sex) values (#{id,jdbcType=BIGINT}, 
  #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, 
  #{birth,jdbcType=TIMESTAMP}, #{sex,jdbcType=BIT})
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.yan.entity.User">
  update tb_users
  <set>
    <if test="username != null">
      username = #{username,jdbcType=VARCHAR}, 
    </if>
    <if test="password != null">
      password = #{password,jdbcType=VARCHAR}, 
    </if>
    <if test="birth != null">
      birth = #{birth,jdbcType=TIMESTAMP},
    </if>
    <if test="sex != null">
      sex = #{sex,jdbcType=BIT}, 
    </if>
  </set>
  where id = #{id,jdbcType=BIGINT}
</update>

其它属性

id 在命名空间中唯一的标识符,可以被用来引用这条语句。

parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。

parameterMap 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType属性。

flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。timeout 设置超时时间,以避免出现死锁问题。这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。

statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。

useGeneratedKeys 仅适用于 insert 和 update,这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。例如 mysql 中的主键为 auto_increment

keyProperty 仅适用于 insert 和 update。指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。

keyColumn 仅适用于 insert 和 update,设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。

databaseId 如果配置了数据库厂商标识,MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。

查询操作

<select id="selectByPrimaryKey" parameterType="java.lang.Long 参数类型" resultMap="BaseResultMap 
所使用的结果映射 id">
    select <include refid="Base_Column_List" /> from tb_users where id = #{id,jdbcType=BIGINT}
</select>

其它属性

id 在命名空间中唯一的标识符,可以被用来引用这条语句。

parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器 TypeHandler 推断出具体传入语句的参数,默认值为未设置(

unset)。

parameterMap 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType属性。

resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。

resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。

flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。

useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为true。

timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。

fetchSize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。

statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。

resultSetType 配置 FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于unset) 中的一个,默认值为 unset (依赖数据库驱动)。

databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。

resultOrdered 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。

resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

动态sql

Mybatis 动态 sql 可以在 xml 映射文件内,以标签的形式编写动态 sql,执行原理是根据表达式的值完成逻辑判断并动态拼接 sql 的功能。

Mybatis 提供了 9 种动态 sql 标签: trim|where|set|foreach|if|choose|when|otherwise|bind。

if

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’ 
  <if test="title != null"> test 中引入的变量实际上就是参数或者参数的属性
    AND title like #{title} 在 select 语句末尾需拼接的 sql 语句,其中#外面的时列名称,#内部的是参数名称
或者参数的属性名称
  </if>
</select>

choose、when、otherwise

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’ 
  <choose> 可以理解为 java 中的开关分支语句,就是 switch
    <when test="title != null"> 条件判断,如果 title 非空,则将标签体的 sql 语句拼接到上面语句的末尾,
拼接完成不继续进行比较其它的 when,这里没有 break 语句。如果条件不成立则继续下一个 when 的比较
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise> 当所有的 when 都不成立时拼接这里的 sql
      AND featured = 1
    </otherwise>
    </choose>
</select>

trim、where、set

<select id="findActiveBlogLike" resultType="Blog">  
  SELECT * FROM BLOG WHERE
  <if test="state != null">state = #{state}</if>
  <if test="title != null">AND title like #{title}</if>
  <if test="author != null and author.name != null">AND author_name like #{author.name}</if>
</select>

如果参数中的 state 属性为 null,但是 title 不为空,则拼接的 sql 语句为 select * from blog where and title like

#{title},很明显是语法错误

改写为:

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG
  <where> 引入 where 标签,可以保证如果有多余的 and 或者 or 之类的条件连接符时会自动删除
    <if test="state != null">state = #{state}</if>
    <if test="title != null">AND title like #{title}</if>
    <if test="author != null and author.name != null">AND author_name like #{author.name}</if>
  </where>
</select>

where 元素只会在子元素返回任何内容的情况下才插入 WHERE 子句。而且,若子句的开头为 AND 或 OR,where 元素也会将它们去除。

可以通过自定义 trim 元素来定制 where 元素的功能:

<trim prefix="WHERE" prefixOverrides="AND |OR "> 在该模块的前面添加 where,前导词 prefix 配置;trim
模块中的内容如果是 and 或者 or 则自动去除 prefixOverrides,其中的|表示或者
  <if test="state != null">state = #{state}</if>
  <if test="title != null">AND title like #{title}</if>
</trim>

用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列

<update id="updateAuthorIfNecessary">
  update Author
    <set> set 标签用于自动生成对应的 set 语句,并自动删除语句块中末尾的,逗号
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

#和$占位符 [面试题]

  • #{}表示一个占位符,向占位符输入参数,mybatis 自动进行 java 类型和 jdbc 类型的转换。程序员不需要考虑参数的类型,比如传入字符串,mybatis 最终拼接好的 sql 就是参数两边加单引号。

  • #{}接收 pojo 数据,可以使用 OGNL 解析出 pojo 的属性值

  • ${}表示 sql 的拼接,通过${}接收参数,将参数的内容不加任何修饰拼接在 sql 中。

  • ${}也可以接收 pojo 数据,可以使用 OGNL 解析出 pojo 的属性值

缺点:不能防止 sql 注入。

<insert id=insert paramterType=com.yan.User>
  Insert into tb_users values (#{username})表示向数据库管理系统提交的 sql 语句为 insert into tb_users
values(?),并在执行前调用 ps.setString 针对?进行赋值
<insert id=insert parameterType=com.yan.User>
  Insert into tb_users values(${username}) 则表示向数据库管理系统提交的 sql 语句为 insert into tb_users
values(‘xiaoma’)

sql 注入问题

例如用户登录 select * from tb_users where username=’zhangsan’ and password=’33333’

但是非法用户输入的数据为特殊内容 1111’ or ‘1’=’1,最终拼接出来的 sql 语句 select * from tb_users where username=’zhangsan’ and password=’1111’ or ‘1’=’1’

解决方案:针对用户输入的内容进行过滤处理,不允许其中包含特殊符号;使用 PreparedStatement 可以在一定程度上避免 sql 注入

万能 Map 类型

当传入的参数过多时不可能把每个属性值都写上,这时候就可以考虑用 Map 方法

<select id="selectByMap" parameterType="map" resultMap="BaseResultMap">
  select * from ${tableName}
  <where>
    <if test="age!=null">year(now())-year(birth)>#{age}</if>
  </where>
</select>

引申的写法

使用实体类型充当参数一般使用较多

例如在接口中添加一个方法 List<T> selectByExample(T row);

<select id="selectByExample" parameterType="com.yan.entity.User" resultMap="BaseResultMap">
  select <include refid="Base_Column_List"/> from tb_users where 1=1
  <if test="username != null">
    and username = #{username,jdbcType=VARCHAR}
  </if>
  <if test="password != null">
    and password = #{password,jdbcType=VARCHAR}
  </if>
  <if test="birth != null">
    and birth = #{birth,jdbcType=DATE}
  </if>
  <if test="sex != null">
    and sex = #{sex,jdbcType=BOOLEAN}
  </if>
</select>

foreach 循环 【面试题】

foreach 元素的属性

1、collection 传入的 List 或 Array 以及 Map

2、item 集合中元素迭代时的别名

3、index 集合中元素迭代的索引

4、open 表示 where 后面以什么开始

5、separator 表示每次进行迭代的分隔符

6、close 表示 where 后面以什么结束

方法 List queryByIds(List ids)

对应的映射元文件:

<select id="queryByIds" resultMap="BaseResultMap" parameterType="list">
  select * from tb_users where id in
  <foreach item="id" index="index" open="(" close=")" separator="," collection="ids">
    #{id}
  </foreach>
</select>

可以将任何可迭代对象,如 List、Set、Map 对象或者数组对象作为集合参数传递foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象或者 Map.Entry对象的集合时,index 是键,item 是值。

模糊查询使用 concat 拼接 sql

<select id="queryLikeName" resultMap="BaseResultMap" parameterType="string">
  select * from tb_users
  <where>
    <if test="name!=null">
      name like concat('%',concat(#{name},'%')) 实际上就是%#{name}%
    </if>
  <where>
</select>

在具体的使用中建议在页面层上对需要使用模糊查询的内容添加%号

其它标签

script 要在带注解的映射器接口类中使用动态 SQL

@Update({"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password},</if>",
" <if test='email != null'>email=#{email},</if>",
" <if test='bio != null'>bio=#{bio}</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);

bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />定义一个变量
  SELECT * FROM BLOG WHERE title LIKE #{pattern} 引用上面定义的变量
</select>
多表关系

在关系型数据库中,表和表之间的关系有 3 种:一对一、一对多或者多对一、多对多。在 MyBatis 中针对表和表之间的关系提供了两个标签

对一关系

例如产品和类目,一般获取类目信息时不需要直接获取产品信息,但是显示产品信息时一般需要类目的名称

create table if not exists tb_catalogs(
  id bigint primary key auto_increment, title varchar(32) not null
)engine=innodb default charset utf8 comment '类目信息';
create table if not exists tb_products(
  id bigint primary key auto_increment, name varchar(32) not null, price numeric(8,2) default 0.0, catalog_id bigint not null comment '外键,用于表达一对多关系', foreign key(catalog_id) references tb_catalogs(id) on delete cascade
)engine=innodb default charset utf8 comment '产品信息';

首先使用 maven 插件进行反向映射

对应的类目的实体类:由于显示类目信息时不需要直接获取产品信息,所以类目类中不包含产品

对应的产品的实体类,由于一般显示产品信息时需要直接显示所属的类别信息,所以产品中应该包含类目信息

修改产品对应的映射文件获取对应的类目信息,具体的实现方式有三种。

方法 1:使用关联查询

修改产品的映射元文件,将需要获取类目数据的查询修改为关联查询

通过在控制台上的日志信息,查看所运行的 SQL 语句

加载数据查看运行结果:不仅获取到了产品信息,同时获取到了类别信息

方法 2:通过 resultMap 发送额外的查询语句

真正所执行的 SQL 语句

关联 association 元素处理有一个类型的关系。需要指定目标属性名以及属性的 javaType,也可以由 MyBatis推断出来,MyBatis 有两种不同的方式加载关联:

方法 2 采用的是嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。

方法 1 采用的是嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。

property映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。实际上就是 product 对象包含一个属性 catalog,这个属性 catalog 是对象类型的,其中又包含多个属性。关联的嵌套 Select 查询:执行需要执行的额外查询

column 用于指定执行关联查询时所需要的参数来源,就是指定执行 select 查询时所使用的参数对应的列。注意:在使用复合主键的时候,可以使用 column=“{prop1=col1,prop2=col2}” 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。

select 用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目 标 select 语 句 。 具 体 请 参 考 下 面 的 例 子 。 注 意 : 在 使 用 复 合 主 键 的 时 候 , 你 可 以 使 用column=“{prop1=col1,prop2=col2}” 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。

fetchType 可选的。有效值为 lazy 和 eager。Lazy 表示延迟加载,eager 表示立即加载。指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。

这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳。这个问题被称为 N+1 查询问题。

实际调用

真正执行的 SQL 语句为

获取所有商品信息只需要一条 SQL 语句,但是获取每个商品的类别信息则需要额外的查询语句,这就是所谓的 1+N 问题。这个问题会导致成百上千的 SQL 语句被执行。

好消息是,MyBatis 能够对这样的查询进行延迟加载 fetch 属性配置,因此可以将大量语句同时运行的开销分散开来。 然而,如果加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。

方法 1 采用的时关联的嵌套结果映射

resultMap 结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。 它可以作为使用额外select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 有部分数据是重复的。 为了将结果集正确地映射到嵌套的对象树中, MyBatis 允许你“串联”结果映射,以便解决嵌套结果集的问题。使用嵌套结果映射的一个例子在表格以后。

columnPrefix当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。 详细说明请参考后面的例子。

notNullColumn 默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。

autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 select 或 resultMap 元素使用。默认值:未设置(unset)。

注意查询中的连接,以及为确保结果能够拥有唯一且清晰的名字,我们设置的别名。 这使得进行映射非常简单。现在我们可以映射这个结果:

非常重要: id 元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。 虽然,即使不指定这个属性,MyBatis 仍然可以工作,但是会产生严重的性能问题。 只需要指定可以唯一标识结果的最少属性。显然,你可以选择主键(复合主键也可以)。

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
  </association>
</resultMap>

指定 columnPrefix 不是 SQL 语句种前缀的含义,就是直接在列名称前添加特殊符号,以便重复使用该结果映射来映射 co-author 的结果。

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
 <association property="coAuthor" resultMap="authorResult" columnPrefix="co_" />
</resultMap>

SQL 语句应该对重复的列名称前面添加 co_,例如原始列名称为 id 则修改为 co_id

方法 3:可以在业务层上自行编码控制对 DAO 的调用。是否查询以及什么时候查询完全由开发人员决定。

对多关系

加载订单基本信息时,需要同时加载对应的订单详项
在这里插入图片描述

对多查询是依赖 collection 实现的

订单实体类,手工添加了一个集合类型的属性
在这里插入图片描述

实现方法 1:集合的嵌套 Select 查询
在这里插入图片描述

调用定义的结果映射
在这里插入图片描述

最终所执行的 SQL 语句出现了嵌套查询的效果
在这里插入图片描述

方法 2:集合的嵌套结果映射
在这里插入图片描述

方法 3:可以在业务层上自行编码控制对 DAO 的调用。是否查询以及什么时候查询完全由开发人员决定。

继承关系的表达

表和表之间的关系没有继承的概念,这是面向对象中的继承,可以定义合理的表结构,用于描述对象之间的继承

父类型:交通工具(id 编码,name 名称)

子类型卡车继承于父类型,同时具有特殊属性载重量

子类型小轿车继承于父类型,同时具有特殊属性载客人数

使用表来表示类之间的继承关系有 3 种不同的表示方式,MyBatis 建议使用单个表来表示

create table tb_vehicle(
   id bigint priamry key auto_increment,
   name varchar(32) not null,
   vehicle_type int default 0 comment '需要引入一个额外的列用于表示该行数据的具体类型', 
   zaizhongliang numeric(3,1) comment '由于当前表种还需要存储轿车数据,所以不能添加 not null 约束' 
   zaikeliang int comment '轿车的特殊属性,不能加 not null 约束' 
)engine=innodb default charset utf8;

鉴别器

<resultMap id="vehicleResult" type="com.yan.entity.Vehicle">
  <id property="id" column="id" />
  <result property="name" column="name"/>
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultType="com.yan.entity.卡车">
      <result property="载重量" column="zaizhongliang" /> 这是卡车特有的属性
    </case>
    <case value="2" resultType="com.yan.entity.轿车">
      <result property="载客人数" column="zaikeliang" />
    </case>
  </discriminator>
</resultMap>

缺点是:可能会有大量的空值列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
mybatis-plus-generator和mybatis-plus是用于简化MyBatis开发的两个工具。mybatis-plus是一个MyBatis的增强工具包,提供了一些便捷的操作,节约了编写简单SQL的时间。而mybatis-plus-generator是一个代码生成器,可以自动生成一些基本的Controller、Service、Mapper和Mapper.xml文件。 通过整合mybatis-plus和mybatis-plus-generator,我们可以更高效地开发项目中的单表增删改查功能。使用mybatis-plus-generator可以自动生成一些基本的文件,例如Controller、Service、Mapper和Mapper.xml,极大地减少了手动创建这些文件的时间和工作量。而mybatis-plus提供的便捷操作可以节约编写简单SQL的时间。 然而,对于一些逻辑复杂、多表操作或动态SQL等情况,建议使用原生SQL来处理。mybatis-plus支持原生SQL的使用,通过写原生SQL可以更灵活地满足这些复杂需求。 综上所述,通过整合mybatis-plus和mybatis-plus-generator,我们可以在开发中更高效地处理单表的增删改查功能,并且对于复杂的需求可以使用原生SQL来满足。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Spring cloud整合MyBatis-plus和mybatis-plus-generator](https://blog.csdn.net/cssweb_sh/article/details/123767029)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [mybatis-plus-generator(mybatisplus代码生成器篇)](https://blog.csdn.net/b13001216978/article/details/121690960)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值