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

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法