MyBatis-2_mybatis2-程序员宅基地

技术标签: java  mybatis  mysql  

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>

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_58414041/article/details/130771920

智能推荐

什么是内部类?成员内部类、静态内部类、局部内部类和匿名内部类的区别及作用?_成员内部类和局部内部类的区别-程序员宅基地

文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别

分布式系统_分布式系统运维工具-程序员宅基地

文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具

用Exce分析l数据极简入门_exce l趋势分析数据量-程序员宅基地

文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量

宁盾堡垒机双因素认证方案_horizon宁盾双因素配置-程序员宅基地

文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置

谷歌浏览器安装(Win、Linux、离线安装)_chrome linux debian离线安装依赖-程序员宅基地

文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖

烤仔TVの尚书房 | 逃离北上广?不如押宝越南“北上广”-程序员宅基地

文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...

随便推点

java spark的使用和配置_使用java调用spark注册进去的程序-程序员宅基地

文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序

汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用_uds协议栈 源代码-程序员宅基地

文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码

AUTOSAR基础篇之OS(下)_autosar 定义了 5 种多核支持类型-程序员宅基地

文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型

VS报错无法打开自己写的头文件_vs2013打不开自己定义的头文件-程序员宅基地

文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件

【Redis】Redis基础命令集详解_redis命令-程序员宅基地

文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令

URP渲染管线简介-程序员宅基地

文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线