MyBatis总结

目录

 

1、历史背景

1.1、jdbc到orm

1.2、引出ORM

1.3、持久层框架

2、MyBatis结构图解

3、MyBatis原理

4、MyBatis内容概括

4.1、mapper映射文件讲解:

4.2、MyBatis主配置文件

一、注册DB连接四要素属性文件

二、指定实体类权限定性类名的别名

三、配置MyBatis的运行环境

四、指定映射文件

4.4、对象讲解

5、动态代理

6、动态SQL

6.1、动态SQL:if 语句

6.2、动态SQL:if+where 语句

6.3、动态SQL:if+set 语句

6.4、动态SQL:choose(when,otherwise) 语句

6.5、动态SQL:trim 语句

6.6、动态SQL: SQL 片段

6.7、动态SQL: foreach 语句

7、关联关系查询

7.1、一对一关联

7.2、一对多关联

7.3、多对多查询

8、延迟加载

8.1、关联对象加载时机

9、查询缓存

9.1、一级查询缓存(只对select语句有效)

9.2、二级缓冲(只对select有效)

9.2.1、二级缓冲关闭

9.2.2、二级缓存的使用原则

10、mybatis注解开发

11、问题:

1:属性名与字段名不一致问题

2:获取新插入语句的ID

3:$与#的区别

4、多参数问题


1、历史背景

1.1、jdbc到orm

JDBC是Java数据库连接技术,所以,他必然根植于Java语言,使用JDBC离不开Java开发环境,是Java语言对于数据库连接的技术实现。JDBC作为一种协议的体现,在Java代码中就是一系列的接口与实现的约定。

使用JDBC只是换了一种方式,不再是手动了,而是借助于Java代码,然后依赖于底层的数据库驱动,去操作数据库、

简言之,你本来是在命令窗口里面,输入一行SQL之后敲回车,现在变成了借助于Java代码,通过几个对象相互配合进而发送SQL,做的所有事情都没有任何变化,查询还是那个查询,更新还是那个更新,变得只是形式。

JDBC作为数据库连接的中间层,将应用程序与数据库连接进行解耦,给开发者提供了极大地方便,从此以后,再也不需要面向数据库驱动进行编程了,只需要面向JDBC进行编程即可,所以JDBC的出现,对于Java连接数据库实现了大一统的局面,解放了生产力。

不得不面对的问题

1、冗余代码

传统的JDBC要花大量的重复 代码在初始化数据库连接上,每次增删改查都要获得Connection对象,初始化Statement,执行得到ResultSet再封装成自己的 List或者Object,这样造成了在每个数据访问方法中都含有大量冗余重复的代码,考虑到安全性的话,还要加上大量的事务控制和log记录。虽然我们 学习了设计模式之后,可以自己定义Factory来帮助减少一部分重复的代码,但是仍然无法避免冗余的问题。

简言之,JDBC功能足够,但是便捷性欠缺,结构化本身没错,结构化模式化流程化才能成为标准,但是必然会产生冗余步骤,如何灵活是一个问题

 2、对象映射

目前存储数据最常用最流行的工具是关系型数据库(还有 列式数据库,键值数据库,图像图形数据库,分布式文档数据库),我们通过JDBC借助于SQL语句操作数据库,但是Java是面向对象的编程语言,所有的操作都是对象,在使用JDBC进行操作时,面向对象的概念却被弱化了

所以看得出来,Java作为纯粹的面向对象编程语言,一切皆是对象,但是目前常用的数据库却是关系型数据库

关系模型就像一张二维表格,因而一个关系型数据库就是由二维表及其之间的联系组成的一个数据组织,这并不是对象型的

JDBC的操作方式是也不是面向对象的,整个过程面向对象编程的思维观念很大程度上被遏制了

1.2、引出ORM

什么是O,R,M?


O(对象模型):实体对象,即我们在程序中根据数据库表结构建立的一个个实体Entity。

R(关系型数据库的数据结构):即我们建立的数据库表。

M(映射):从R(数据库)到O(对象模型)的映射,可通过XML文件映射。

鉴于以上提出来的问题,在使用Java开发时,我们希望真正的建立一个对象型数据库,或者说至少使用起来看起来像一个对象型数据库,但是,目前常用的数据库又的确是关系型数据库,这一点短期内又无法改变。

所以出现了ORM,对象关系映射(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping)

ORM到底做什么?

JDBC将应用程序开发者与底层数据库驱动程序进行解耦,作为中间层承上启下

而ORM是插入在应用程序与JDBCAPI之间的一个中间层,JDBC并不能很好地支持面向对象的程序设计

ORM解决了这个问题,通过JDBC将字段高效的与对象进行映射

应用程序开发人员不再需要直接与JDBC API进行打交道了,可以使用更加便利的ORM工具,提高开发效率

所以ORM是干什么的?

ORM用于完成Java对象与关系型数据库的映射,是JDBC的一层封装,提高了易用性。

简言之,ORM工具就是JDBC的封装,简化了JDBC的使用,完成关系型数据库中数据与Java对象的映射。

image_5c4aa710_22d8

ORM工具框架最大的核心就是封装了JDBC的交互,你不在需要处理结果集中的字段或者行或者列,借助于ORM可以快速进行开发,而无需关注JDBC交互细节,

但是既然是JDBC的封装,多一层封装,就势必会带来性能的开销,这是无法回避的事实,不过现在技术不断发展,性能开销越来越小。

他们往往都附加了更多的功能,比如很多ORM框架也叫做持久化ORM框架

什么意思呢?

持久化简单理解就是将数据脱离内存可以独立保存,保存到数据库,保存到电脑硬盘文件等等形式,都是持久化

“持久化ORM框架”中的持久化一般是指保存到数据库,所以说如果一个ORM提供了CRUD操作API,应用程序可以借助于ORM完成数据持久化的操作,这就算是一个持久化ORM框架

就如同很多DataSource的实现中添加了很多功能,有些就直接被叫做数据库连接池

所以说具体怎么讲,都是字面的含义,真正需要做的是理解ORM思想的含义:

完成对象与关系型数据库的映射,封装底层与数据库的交互,并且很多都提供了强大的附加功能,比如持久化

现在的ORM基本上都是包括对持久类对象进行CRUD操作的API

对于Java来说,常用的有HibernateMybatis(iBatis)还有Spring JDBC等,在ORM核心思想的基础上周边又做了很多事情

所以说基本上很少有人直接使用原生的JDBC,可能有的公司中不会使用这些框架,因为毕竟框架的引入会牺牲性能

而且框架是作为JDBC的封装,就好比一个工具类,而且是别人封装的工具类,终归有些地方可能有的人用的不顺手,或者说不适合有些场景,大公司有些会自己研发一套自己需要的类ORM工具,自己使用

ORM框架各有千秋利弊,你可以不用各种已成的框架,但是,没有任何人可以否定ORM背后的思想,ORM会一定程度上降低性能但是借助于代码生成工具等可以极大地提高开发效率

而且,ORM工具有极强的可维护性,虽然会降低性能,但是更多的时候可能是代码不够完美,算法不够高明,逻辑不够清晰,所以负责任的说ORM在很多场景都是很好的一种选择。

1.3、持久层框架

企业应用中* 数据很重要(各种订单数据、客户数据、库存数据之类的),比应用程序本身更重要, 所以需要把数据持久化。持久化可以通过很多方式,写文件和数据库都可以。只是现在企业一般都会选择把数据持久化到数据库中,因为可以很方便的查询统计分析,但数据库的数据最终还是会写到磁盘上的。

上面已经讲了持久层框架流程的有Hibernate和Mybatis(iBatis)还有Spring JDBC。下面来介绍一下这三个框架的区别:

JDBC

       首先是JDBC,这个是Java语言提供的规范,原生操作数据库。主要就是定义一些接口和通讯类,接口定义好之后,各个数据库厂商来提供具体的实现,比如Oracle,Mysql等,这些厂商都有自己的JDBC具体实现,当然,我们也可以自己实现一个,不过成本比较高。对于JDBC ,具体实现步骤: ①.载入JDBC驱动程序    ②.定义连接URL   ③.建立连接   ④.创建Statement对象  ⑥.执行查询或更新F.结果处理   ⑦.关闭连接    JDBC主要的优点是原生操作数据库,工作效率高(用得好的情况下),使用起来也比较灵活 ,缺点呢也相对来说比较明显,开发起来代码比较繁重、重复太多、可扩展性不够好。  

 Mybatis

       然后是 Mybtais (Ibatis),前身为mybatis,这是一个半自动化的ORM框架,具体实现需要我们自己写SQL语句,主要特点是把SQL语句和Java的Field做映射,通过parameterMap和resultMap来做映射,所以,Mybtais 使用起来也是比较灵活的,可以自己写Sql,并且如果你家公司有高手DBA,交给他来优化或者写SQl也是很不错的选择。优点:使用灵活、方便sql调优、轻量级学习成本相对较低、降低耦合度。缺点呢:SQL语句的编写工作量较大,尤其是字段多、关联表多时,更是如此,对开发人员编写SQL语句的功底有一定要求。SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

 Hibernate

       和Mybtais对比的比较多的就是hibernate了,这可以算是一个自动化的ORM框架,完全实现啦操作数据的面向对象话。自带HQL语句解释器,利用这个特性,开发人员可以认真写HQl语句就可以了,只要在不同的数据库中使用不同的驱动,这样就可以比较方便的在不同的DB上切换或者移植 ,但是有些比较复杂的SQL语句在转换为HQL语句的时候还是比较有难度的,如果没有hibernate开发高手,个人觉得还是使用Mybtais比较好。优点:提供大量封装基于JDBC效率相对较高、完全面向对象、更好的移植性、存在缓存机制(session缓存,二级缓存,查询缓存),对于那些改动不大且经常使用的数据,可以将它们放到缓存中,不必在每次使用时都去查询数据库,缓存机制对提升性能大有裨益。

Hibernate优势

  1. Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。

  2. Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。

  3. Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。

  4. Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。

Mybatis优势

  1. MyBatis可以进行更为细致的SQL优化,可以减少查询字段。

  2. MyBatis容易掌握,而Hibernate门槛较高。

 SpringJDBC

      spring 的核心思想是IOC和AOP,但是为了和其他框架竞争,Spring自己也实现了一套JDBC的东西,其核心接口和实现都是给JdbcTemplate 和 NamedParameterJdbcTemplate这两个接口提供服务的。使用的过程中也可以简化一些开发的代码量,并且Spring本身对事物提供强大的支持能力,对于服务层框架Spring来说 持久层的SpringJDBC 可以更完美的和Spring框架无缝衔接。

2、MyBatis结构图解

1、 mybatis配置

SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。

mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

SqlMapConfig.xml是mybatis的核心文件。mybatis将dao层与sql语句分离开来,虽然写的时候分离开来了,但是执行的时候还是要依靠sql语句,所以我们的sql语句写在Mapper.xml中。我们在加载核心的时候,会加载他下面的Mapper.xml,所以sql语句便会加载进去了。我们只需要在SqlMapConfig.xml中引入Mapper.xml就可以了,所以最后只需要加载SqlMapConfig.xml这一个核心配置文件。

2、 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂。工厂能帮我们去加载核心配置文件。加载了核心配置文件后就创建session,通过session可以对数据库进行操作。

3、 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

4、 mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。Executor是执行者,我们不需要管,因为mybatis已经为我们封装好了。mybatis直接执行sql语句。

5、 Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

6、 Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

7、 Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

8、Mapped Statement是输入与输出中间过程中产生的一些对象,通过这些对象去访问数据库。


3、MyBatis原理

四大对象:
      executor、parameterHandler、ResultSetHandler、StatementHandler

 
细化
1、获取sqlsessionFactory

     解析文件的信息保存到Configuration中,返回Configuraation的DefaultSqlSession对象;【mappedStatement】代表一个增删改查的详细信息

2、获取sqlSession

3、获取接口代理对象MapperProxy

4、执行增删改查

 

4、MyBatis内容概括

4.1、mapper映射文件讲解:

SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):

  • cache – 给定命名空间的缓存配置。
  • cache-ref – 其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

4.1.1、resultmap构成元素

元素子元素 作用
constructoridArg 、arg用于配置构造器方法
id         将结果集标记为id,以方便全局调用
result         配置POJO到数据库列名映射关系
association    级联使用代表一对一关系
collection    级联使用代表一对多关系
discriminator  级联使用鉴别器 根据实际选择实例,可以通过特定条件确定结果集

id和result元素
以User类为例:

class User{
    private int userId;
    private String name;
    public User(long userId,String name){
        this.userId = userId;
        this.name = name;
        }
}

id、result是最简单的映射,id为主键映射;result其他基本数据库表字段到实体类属性的映射。resultmap的xml如下:

<resultMap type="User" id="userMap">  
    <id  property="UserId" column="user_id" javaType="int" jdbcType="int"/>  
    <result property="name" column="name" javaType="String" jdbcType="VARCHAR"/>
</resultMap>

id、result语句属性配置细节:

属性    描述
property    需要映射到JavaBean 的属性名称。
column    数据表的列名或者标签别名。
javaType    一个完整的类名,或者是一个类型别名。如果你匹配的是一个JavaBean,那MyBatis 通常会自行检测到。然后,如果你是要映射到一个HashMap,那你需要指定javaType 要达到的目的。
jdbcType    数据表支持的类型列表。这个属性只在insert,update 或delete 的时候针对允许空的列有用。JDBC 需要这项,但MyBatis 不需要。如果你是直接针对JDBC 编码,且有允许空的列,而你要指定这项。
typeHandler    使用这个属性可以覆写类型处理器。这项值可以是一个完整的类名,也可以是一个类型别名


CONSTRUCTOR 构造器
在resultMap中,通常使用id、result子元素把Java实体类的属性映射到数据库表的字段上。但是如果在遇到JavaBean没有无参构造函数时,我还需要使用构造器元素实现一个JavaBean的实例化和数据注入。 
再以User为例,那么对应的resultmap就需要添加构造器:

<constructor>
    <idArg column="user_id" javaType="long"/>
    <arg column="name" javaType="String"/>
</constructor>

定义java实体类的属性映射到数据库表的字段上。我们也可以使用实体类的构造方法来实现值的映射,这是通过构造方法参数的书写的顺序来进行赋值的。 
这样MyBatis就知道需要用这个构造方法构造了。

结果集处理方法
1. 使用map储存结果集
一般情况下,所有select语句都可以使用map储存,但是使用map就意味着可读性的下井,所以这不是推荐的方式。

<select id="findUserById" parameterType="long" resultType="map">
        select user_id ,name form user where user_id=#{userId}
</select>

2. 使用POJO储存结果集(推荐)
一般我们都使用POJO储存查询结果。我们可以使用select自动映射,还可以使用select语句中的resultMap属性配置映射集合,不过需要提前定义resultMap。 
那我们就可以将之前的select语句修改:

<select id="findUserById" parameterType="long" resultMap="userMap">
        select user_id ,name form user where user_id=#{userId}
</select>

3.级联
在数据库中包含着一对多、一对一的关系。比如说一个人和他的身份证就是一对一的关系,但是他和他的银行卡就是一对多的关系。我们的生活中存在着很多这样的场景。我们也希望在获取这个人的信息的同时也可以把他的身份证信息一同查出,这样的情况我们就要使用级联。在级联中存在3种对应关系。 
- 一对一的关系 
- 一对多的关系 
- 多对多的关系(这种情况由于比较复杂,我们通常会使用双向一对多的关系来降低复杂度)

1.association 一对一级联其中类型使用javaType

对象中有个对象属性
方法:association 指定联合的javaBean对象,存放对象属性

  • property 指定哪个属性是联合的对象
  • javaType 指定这个属性对象的类型

重点:使用association进行分步查询

  •     ssociation定义关联对象的封装规则
  •     select:表明当前属性是调用select指定的方法查出的结果
  •     column:指定将哪一列的值传给这个方法

流程: select 指定方法 (传入column制定的这列参数的值)查出对象 并封装给property指定的对象


2.collection 一对多级联其中类型使用ofType

对象中有个对象的List集合
方法:collection标签存放对象集合
        属性和传参和association 基本一样。。
有两个注意的地方:

1:如何传递多列的参数: 
     将多列的值封装map传递  column=“{key1=column1,key2=column2}”:

2:collection还可以局部修改是否延迟加载 
    fetchType=”lazy”:表示使用延迟加载;

       - lazy:延迟  //只要当需要的时候才会加载属性
       - eager:立即

<!-- 
        根据classId查询对应的班级信息,包括学生,老师
     -->
    <!-- 
    方式一: 嵌套结果: 使用嵌套结果映射来处理重复的联合结果的子集
    SELECT * FROM class c, teacher t,student s WHERE c.teacher_id=t.t_id AND c.C_id=s.class_id AND  c.c_id=1

     -->
    <select id="getClass3" parameterType="int" resultMap="ClassResultMap3">
        select * from class c, teacher t,student s where c.teacher_id=t.t_id and c.C_id=s.class_id and  c.c_id=#{id}
    </select>
    <resultMap type="me.gacl.domain.Classes" id="ClassResultMap3">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" column="teacher_id" javaType="me.gacl.domain.Teacher">
            <id property="id" column="t_id"/>
            <result property="name" column="t_name"/>
        </association>

        <!-- ofType指定students集合中的对象类型 -->
        <collection property="students" ofType="me.gacl.domain.Student">
            <id property="id" column="s_id"/>
            <result property="name" column="s_name"/>
        </collection>

    </resultMap>
    
    <!-- 
        方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型,延迟加载专用(推荐)
            SELECT * FROM class WHERE c_id=1;
            SELECT * FROM teacher WHERE t_id=1   //1 是上一个查询得到的teacher_id的值
            SELECT * FROM student WHERE class_id=1  //1是第一个查询得到的c_id字段的值
     -->

     <select id="getClass4" parameterType="int" resultMap="ClassResultMap4">
        select * from class where c_id=#{id}
     </select>
     <resultMap type="me.gacl.domain.Classes" id="ClassResultMap4">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" column="teacher_id" javaType="me.gacl.domain.Teacher" select="getTeacher2"></association>
        <collection property="students" ofType="me.gacl.domain.Student" column="c_id" select="getStudent"></collection>
     </resultMap>
     
     <select id="getTeacher2" parameterType="int" resultType="me.gacl.domain.Teacher">
        SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}
     </select>
     
     <select id="getStudent" parameterType="int" resultType="me.gacl.domain.Student">
        SELECT s_id id, s_name name FROM student WHERE class_id=#{id}
     </select>

4.4.2、select(查询)

查询语句是 MyBatis 中最常用的元素之一,光能把数据存到数据库中价值并不大,如果还能重新取出来才有用,多数应用也都是查询比修改要频繁。对每个插入、更新或删除操作,通常对应多个查询操作。这是 MyBatis 的基本原则之一,也是将焦点和努力放到查询和结果映射的原因。简单查询的 select 元素是非常简单的。比如:

<select id="selectPerson" parameterType="int" resultType="hashmap">

    SELECT * FROM PERSON WHERE ID = #{id}

</select>

这个语句被称作 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。

注意参数符号:

#{id}

这就告诉 MyBatis 创建一个预处理语句参数,通过 JDBC,这样的一个参数在 SQL 中会由一个"?"来标识,并被传递到一个新的预处理语句中,就像这样:

    // Similar JDBC code, NOT MyBatis…

    String selectPerson = "SELECT * FROM PERSON WHERE ID=?";

    PreparedStatement ps = conn.prepareStatement(selectPerson);

    ps.setInt(1,id);

当然,这需要很多单独的 JDBC 的代码来提取结果并将它们映射到对象实例中,这就是 MyBatis 节省你时间的地方。我们需要深入了解参数和结果映射,细节部分我们下面来了解,select 元素有很多属性允许你配置,来决定每条语句的作用细节:

属性

描述

id

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

parameterType

将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。

resultType

从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。

resultMap

外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用。

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 中的一个,默认值为 unset (依赖驱动)。

databaseId

如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。

resultOrdered

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

resultSets

这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。

4.1.3、insert, update 和 delete 语句

数据变更语句 insert,update 和 delete 的实现非常接近:

<insert

    id="insertAuthor"

    parameterType="domain.blog.Author"

    flushCache="true"

    statementType="PREPARED"

    keyProperty=""

    keyColumn=""

    useGeneratedKeys=""

    timeout="20">

   

<update

    id="updateAuthor"

    parameterType="domain.blog.Author"

    flushCache="true"

    statementType="PREPARED"

    timeout="20">

   

<delete

    id="deleteAuthor"

    parameterType="domain.blog.Author"

    flushCache="true"

    statementType="PREPARED"

    timeout="20">

属性

描述

id

命名空间中的唯一标识符,可被用来代表这条语句。

parameterType

将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。

flushCache

将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。

timeout

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

statementType

STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。

useGeneratedKeys

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

keyProperty

(仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。

keyColumn

(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。

databaseId

如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。

首先,如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys="true",然后再把 keyProperty 设置到目标属性上就OK了。例如,如果上面的 Author 表已经对 id 使用了自动生成的列类型,那么语句可以修改为:

<insert id="insertAuthor" useGeneratedKeys="true"

        keyProperty="id">

    insert into Author (username,password,email,bio)

    values (#{username},#{password},#{email},#{bio})

</insert>

如果你的数据库还支持多行插入, 你也可以传入一个Authors数组或集合,并返回自动生成的主键:

<insert id="insertAuthor" useGeneratedKeys="true"

        keyProperty="id">

    insert into Author (username, password, email, bio) values

    <foreach item="item" collection="list" separator=",">

        (#{item.username}, #{item.password}, #{item.email}, #{item.bio})

    </foreach>

</insert>

对于不支持自动生成类型的数据库或可能不支持自动生成主键 JDBC 驱动来说,MyBatis 有另外一种方法来生成主键。 这里有一个简单(甚至很傻)的示例,它可以生成一个随机 ID(你最好不要这么做,但这里展示了 MyBatis 处理问题的灵活性及其所关心的广度):

<insert id="insertAuthor">

    <selectKey keyProperty="id" resultType="int" order="BEFORE">

        select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1

    </selectKey>

    insert into Author

        (id, username, password, email,bio, favourite_section)

    values

        (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})

</insert>

在上面的示例中,selectKey 元素将会首先运行,Author 的 id 会被设置,然后插入语句会被调用。这给你了一个和数据库中来处理自动生成的主键类似的行为,避免了使 Java 代码变得复杂。 selectKey 元素描述如下:

属性

描述

keyProperty

selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。

keyColumn

匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。

resultType

结果的类型。MyBatis 通常可以推算出来,但是为了更加确定写上也不会有什么问题。MyBatis 允许任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。

order

这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素 - 这和像 Oracle 的数据库相似,在插入语句内部可能有嵌入索引调用。

statementType

与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。

4.1.4、Sql

这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。它可以被静态地(在加载参数) 参数化. 不同的属性值通过包含的实例变化.,比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

这个 SQL 片段可以被包含在其他语句中,例如:

<select id="selectUsers" resultType="map">

     select

     <include refid="userColumns"><property name="alias" value="t1"/></include>,

     <include refid="userColumns"><property name="alias" value="t2"/></include>

     from some_table t1

     cross join some_table t2

</select>

属性值可以用于包含的refid属性或者包含的字句里面的属性值,例如:

<sql id="sometable">

    ${prefix}Table

</sql>

<sql id="someinclude">

    from

        <include refid="${include_target}"/>

</sql>

<select id="select" resultType="map">

    select

        field1, field2, field3

    <include refid="someinclude">

        <property name="prefix" value="Some"/>

        <property name="include_target" value="sometable"/>

    </include>

</select>

4.1.5、参数(Parameters)

前面的所有语句中你所见到的都是简单参数的例子,实际上参数是 MyBatis 非常强大的元素,对于简单的做法,大概 90% 的情况参数都很少,比如:

    <select id="selectUsers" resultType="User">

     select id, username, password

     from users

     where id = #{id}

    </select>

上面的这个示例说明了一个非常简单的命名参数映射。参数类型被设置为 int,这样这个参数就可以被设置成任何内容。原生的类型或简单数据类型(比如整型和字符串)因为没有相关属性,它会完全用参数值来替代。然而,如果传入一个复杂的对象,行为就会有一点不同了。比如:

    <insert id="insertUser" parameterType="User">

     insert into users (id, username, password)

     values (#{id}, #{username}, #{password})

    </insert>

如果 User 类型的参数对象传递到了语句中,id、username 和 password 属性将会被查找,然后将它们的值传入预处理语句的参数中。

这点对于向语句中传参是比较好的而且又简单,不过参数映射的功能远不止于此。首先,像 MyBatis 的其他部分一样,参数也可以指定一个特殊的数据类型,配置示例:

    #{property,javaType=int,jdbcType=NUMERIC}

像 MyBatis 的typeHandler部分一样,javaType 通常可以从参数对象中来去确定,前提是只要对象不是一个 HashMap。那么 javaType 应该被确定来保证使用正确类型处理器。为了以后定制类型处理方式,你也可以指定一个特殊的类型处理器类(或别名),比如:

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

对于数值类型,还有一个小数保留位数的设置,来确定小数点后保留的位数。

#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

最后,mode 属性允许你指定 IN,OUT 或 INOUT 参数。如果参数为 OUT 或 INOUT,参数对象属性的真实值将会被改变,就像你在获取输出参数时所期望的那样。如果 mode 为 OUT(或 INOUT),而且 jdbcType 为 CURSOR(也就是 Oracle 的 REFCURSOR),你必须指定一个 resultMap 来映射结果集到参数类型。要注意这里的 javaType 属性是可选的,如果左边的空白是 jdbcType 的 CURSOR 类型,它会自动地被设置为结果集。 尽管所有这些强大的选项很多时候你只简单指定属性名,其他的事情 MyBatis 会自己去推断,最多你需要为可能为空的列名指定 jdbcType。

字符串替换

默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值(比如?)。这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用:

ORDER BY ${columnName}

这里 MyBatis 不会修改或转义字符串,以这种方式接受从用户输出的内容并提供给语句中不变的字符串是不安全的,会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。

4.1.6、多参数传递

MyBatis中的映射语句有一个parameterType属性来制定输入参数的类型。如果我们想给映射语句传入多个参数的话,我们可以将所有的输入参数放到HashMap中,将HashMap传递给映射语句。MyBatis 还提供了另外一种传递多个输入参数给映射语句的方法。假设我们想通过给定的name和email信息查找学生信息,定义查询接口如下:

Public interface StudentMapper

{

        List<Student> findAllStudentsByNameEmail(String name, String email);

}

MyBatis 支持将多个输入参数传递给映射语句,并以#{param}的语法形式引用它们:

<select id="findAllStudentsByNameEmail" resultMap="StudentResult">

        select stud_id, name,email, phone from Students

                where name=#{param1} and email=#{param2}

</select>

这里#{param1}引用第一个参数name,而#{param2}引用了第二个参数email

还可以使用 @Param 注解来给参数命名,定义查询接口如下:

Public interface StudentMapper

{

        List<Student> findAllStudentsByNameEmail(@Param("name") String name, @Param("email") String email);

}

MyBatis 配置文件可以直接使用命名的参数,如下配置:

<select id="findAllStudentsByNameEmail" resultMap="StudentResult">

        select stud_id, name,email, phone from Students

                where name=#{name} and email=#{email}

</select>

4.1.7、存储过程和输入参数

存储过程的调用必须设置 statementType="CALLABLE",并且存储过程的调用语法为 { call proc_name} 如果有输出参数,则还需要设置参数的 mode,示例如下:

  1. 存储过程:

    CREATE DEFINER=`dev`@`%` PROCEDURE `procBuild_BillNo`(in prefix varchar(8),in serial varchar(10),out billNo varchar(20))

    BEGIN

        SET billNo = CONCAT(prefix,serial);

    END

  2. 方法定义:

    void BuildBillNo(HashMap<String,Object> map);

    注意:存储过程的输出参数,只能通过传入的 HashMap 来获取

  3. Mapper配置:

    <select id="BuildBillNo" parameterType="map" statementType="CALLABLE">

            {call procBuild_BillNo(#{prefix},#{serial},#{billNo,mode=OUT,jdbcType=VARCHAR})}

    </select>

    注意:存储过程调用,其 statementType 必须设置为 CALLABLE;存储过程/函数的返回结果需要使用 #{参数}=call procName(?,?...) 参数接收,并且需要指定对应的mode为 OUT 类型和 jdbcType 类型

  4. Java代码调用:

    HashMap<String, Object> map = new HashMap<String, Object>();

    map.put("prefix", "H");

    map.put("serial", "223232323");

    mapper.BuildBillNo(map);

    System.out.println("BuildBillNo is " + map.get("billNo"));

  5. 自动映射

    当自动映射查询结果时,MyBatis会获取sql返回的列名并在java类中查找相同名字的属性(忽略大小写)。 这意味着如果Mybatis发现了ID列和id属性,Mybatis会将ID的值赋给id。 通常数据库列使用大写单词命名,单词间用下划线分隔;而java属性一般遵循驼峰命名法。 为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase设置为true。 自动映射甚至在特定的result map下也能工作。在这种情况下,对于每一个result map,所有的ResultSet提供的列, 如果没有被手工映射,则将被自动映射。自动映射处理完毕后手工映射才会被处理。

    通过配置 autoMappingBehavior 来设置自动映射等级,有三种自动映射等级:

  • NONE - 禁用自动映射。仅设置手动映射属性。
  • PARTIAL - 将自动映射结果除了那些有内部定义内嵌结果映射的(joins)(默认)
  • FULL - 自动映射所有。

默认值是PARTIAL,这是有原因的。当使用FULL时,自动映射会在处理join结果时执行,并且join取得若干相同行的不同实体数据,因此这可能导致非预期的映射。

原文:https://www.cnblogs.com/li3807/p/7061840.html

4.2、MyBatis主配置文件

主配置文件可以随便命名,其主要完成以下几个功能:

  1. 注册存放DB连接四要素的属性文件
  2. 注册实体类的权限定性类名的别名
  3. 配置MyBatis运行环境,即数据源与事务管理器
  4. 注册映射文件

MyBatis.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>

    <!-- 注册属性文件 -->
    <properties resource="jdbc.properties"/>
    <!-- 配置MyBatis运行环境 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.dirver}"/>  
                <property name="url" value="${jdbc.url}"/>  
                <property name="username" value="${jdbc.user}"/>  
                <property name="password" value="${jdbc.password}"/>  
            </dataSource>
        </environment>
    </environments>
    
    <!-- 注册映射文件 -->
    <mappers>  
        <mapper resource="com/hcx/dao/mapper.xml"/>   
        <!-- <mapper resource="com/hcx/dao/mapper2.xml"/>  -->
    </mappers>
</configuration>

一、注册DB连接四要素属性文件

<!-- 注册DB连接四要素的属性文件 -->
<properties resource="jdbc.properties"/>

二、指定实体类权限定性类名的别名

1.<package/>方式

一般使用<package/>方式,这样做的好处是会将该包中年所有实体类的简单类名指定为别名。

<!--配置别名-->
<typeAliases>
    <package name="com.hcx.beans"/>
</typeAliases>

2.通过<typealias/>指定。

  • type:权限定性类名
  • alias:别名

该方式的好处是,可以指定别名为简单类名以外的其他名称。当然,弊端是,必须逐个指定,比较繁琐。

<!-- 注册类的别名 -->
<typeAliases>
    <typeAlias type="com.hcx.beans.Student" alias="Student"/>
</typeAliases>

3.使用MyBatis内置的类型别名

基本类型:

基本类型.PNG

常用包装类型:

常用包装类.PNG

三、配置MyBatis的运行环境

主要是配置数据源和事务管理器

1.<environments/>标签

在<environments/>中可以包含多个运行环境<environment/>,但其default属性指定了当前MyBatis运行时所选择使用的环境。

environments标签.PNG

2.<transactionManager/>标签

<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />

该标签用于指定MyBatis所使用的事务管理器。MyBatis支持两种事务管理器;JDBC与MANAGED

  • JDBC:使用jdbc事务管理机制。即,通过connection的commit()方法提交,通过rollback方法回滚。但默认情况下,MyBatis将自动提交功能关闭了,改为了手动提交。即程序中需要显式的对事务进行提交或回滚。
  • MANAGED:由容器来管理事务的整个生命周期(如Spring容器)

3.<dataSource/>标签

该标签用于配置MyBatis使用的数据源类型与数据库连接基本属性。常见类型有:UNPOOLED、POOLED、JDNI等

  • UNPOOLED:不使用连接池。即每次请求,都会为其创建一个DB连接,使用完毕后,会马上将此连接关闭。
  • POOLED:使用数据库连接池来维护连接
  • JDNI:数据源可以定义到应用的外部,通过JDNI容器获取数据库连接。

代码:

<dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>  
    <property name="url" value="jdbc:mysql://localhost:3306/test"/>  
    <property name="username" value="root"/>  
    <property name="password" value="root"/>  
</dataSource>

若要从属性文件中读取DB连接四要素信息,则使用如下方式:

<!-- 数据库连接池 -->
<dataSource type="POOLED">
    <property name="driver" value="${jdbc.dirver}"/>  
    <property name="url" value="${jdbc.url}"/>  
    <property name="username" value="${jdbc.user}"/>  
    <property name="password" value="${jdbc.password}"/>  
</dataSource>

四、指定映射文件

指定映射文件的方式有多种,但所有的方式,都是指定在<mappers/>标签中的。

1.<mapper resource="">方式

<!-- 注册映射文件 -->
<mappers>  
    <mapper resource="com/hcx/dao/mapper.xml"/>   
    <mapper resource="com/hcx/dao/mapper2.xml"/>   
</mappers>

2.<mapper url="">方式

<mappers>
    <mapper url="file:///E:\workspace\mybatisPrimary\src\com\hcx\dao\IStudentDao.xml"/>
</mappers>

该方式的好处是,可以将映射文件放在本地或网络的任意位置,通过url地址即可直接访问。当通常映射文件是存放在当前应用中的,所以该方式不常用。

3.<mapper class="">方式

class属性值为dao接口的全类名

<mappers>  
    <mapper class="com.hcx.dao.IStudentDao"/>        
</mappers>

该方式的使用,需要满足以下几个要求:

  1. 映射文件名要与dao接口名相同
  2. 映射文件要与接口在同一包中
  3. 映射文件中<mapper/>的namespace属性值为dao接口的全类名

4.<package name="">方式

当映射文件较多时,也可以使用如下形式。其中package的naem属性指定映射文件所存放的包。

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

但,这种方式的使用需要满足以下条件:

  1. dao使用mapper动态代理实现
  2. 映射文件名要与dao几口名相同
  3. 映射文件要与接口在同一包中
  4. 映射文件中<mapper/>的namespace属性值为dao接口的全类名

原文链接:https://www.jianshu.com/p/4191d2de04b7

4.4、对象讲解

4.4.1、SqlSessionFactory

SqlSessionFactory是MyBatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜像.SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象的build方法获得,而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例.每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心.同时SqlSessionFactory也是线程安全的,SqlSessionFactory一旦被创建,应该在应用执行期间都存在.在应用运行期间不要重复创建多次,建议使用单例模式.SqlSessionFactory是创建SqlSession的工厂.

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

由于使用到了io流inputstream,需要关闭,但是这里注意的是build()方法会自动将输出流关闭。。。

4.4.2、SqlSession

SqlSession是一个面向用户(程序员)的接口。
SqlSession中提供了很多操作数据库的方法:如selectOne(返回单个记录)、selectList(返回单个或多个记录)。
SqlSession是线程不安全的,在SqlSession实现类中除了有接口中的方法除了操作数据库的方法,还有数据库属性,导致线程不安全。
SqlSession最佳应用场合在方法体内,因为每个线程对相同的方法也有不同的内存区域。这种线程不安全的变量只能放在方法内部,定义成局部变量。

selectOne 和 selectList 的不同仅仅是 selectOne 必须返回一个对象。 如果多余一个, 或者 没有返回 (或返回了 null) 那么就会抛出异常。 , 如果你不知道需要多少对象, 使用 selectList。

无参openSession()方法执行时,自动将事务提交关闭,这就是为何需要手动提交commint、

sqlsession的insert()、delete()、update()方法底层执行的都是update方法

5、动态代理

mybatis最令人印象深刻的特性恐怕就是interface与mapper的映射了。开发者只需要声明接口,并编写对应在xml中的sql,一个可以提供服务的dao层功能就完成了,竟然不需要编写interface的实现类。这个感觉起来非常神奇也令人疑惑不解的特性正是利用jdk的动态代理技术实现的。事实上,mybatis内部使用了多种动态代理技术,包括jdk自带、javassist、cglib等,这篇文章主要围绕mybatis interface的实现来展开,即jdk动态代理在mybatis中的应用

Mapper接口开发必须遵循的规范

  • 1.Mapper.xml文件中的【namespace】必须与Mapper.java接口【类路径】相同
  • 2.Mapper.java接口中的【方法名】必须与Mapper.xml中对应的【id】相同
  • 3.Mapper.java接口中的【入参】必须与Mapper.xml中对应的【parameter】相同
  • 4.Mapper.java接口中的【返回类型】必须与Mapper.xml中对应的【resultType】相同

获取动态代理对象:

private IStudentDao dao;

private SqlSession session;

session=MyBatisutils.getSession();

dao=session.getMapper(IStudentDao.class);

dao.methodName();

session.commit();   session.close();

mybatis对于dao的自动查询,底层调用的只有selectOne()和selectList()方法,没有selectMap方法,因此返回为map的不可再动态代理中实现,会报错。。。如果接受类型为list则自动调用selectList()方法,否则为selectOne();

6、动态SQL

MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。再有就是在程序运行期间,根据用户提交的查询条件进行查询,查询条件不一样,执行的SQL语句也就不一样,例如买车票查询。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。  

动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。

6.1、动态SQL:if 语句

  根据 username 和 sex 来查询数据。如果username为空,那么将只根据sex来查询;反之只根据username来查询

  首先不使用 动态SQL 来书写

1

2

3

4

5

6

<select id="selectUserByUsernameAndSex"

        resultType="user" parameterType="com.ys.po.User">

    <!-- 这里和普通的sql 查询语句差不多,对于只有一个参数,后面的 #{id}表示占位符,里面不一定要写id,

            写啥都可以,但是不要空着,如果有多个参数则必须写pojo类里面的属性 -->

    select * from user where username=#{username} and sex=#{sex}

</select>

  

  上面的查询语句,我们可以发现,如果 #{username} 为空,那么查询结果也是空,如何解决这个问题呢?使用 if 来判断

1

2

3

4

5

6

7

8

9

10

<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">

    select * from user where

        <if test="username != null">

           username=#{username}

        </if>

         

        <if test="username != null">

           and sex=#{sex}

        </if>

</select>

  这样写我们可以看到,如果 sex 等于 null,那么查询语句为 select * from user where username=#{username},但是如果usename 为空呢?那么查询语句为 select * from user where and sex=#{sex},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句

6.2、动态SQL:if+where 语句

1

2

3

4

5

6

7

8

9

10

11

12

<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">

    select * from user

    <where>

        <if test="username != null">

           username=#{username}

        </if>

         

        <if test="username != null">

           and sex=#{sex}

        </if>

    </where>

</select>

  这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。

6.3、动态SQL:if+set 语句

  同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<!-- 根据 id 更新 user 表的数据 -->

<update id="updateUserById" parameterType="com.ys.po.User">

    update user u

        <set>

            <if test="username != null and username != ''">

                u.username = #{username},

            </if>

            <if test="sex != null and sex != ''">

                u.sex = #{sex}

            </if>

        </set>

     

     where id=#{id}

</update>

  这样写,如果第一个条件 username 为空,那么 sql 语句为:update user u set u.sex=? where id=?

      如果第一个条件不为空,那么 sql 语句为:update user u set u.username = ? ,u.sex = ? where id=?

 

6.4、动态SQL:choose(when,otherwise) 语句

  有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<select id="selectUserByChoose" resultType="com.ys.po.User" parameterType="com.ys.po.User">

      select * from user

      <where>

          <choose>

              <when test="id !='' and id != null">

                  id=#{id}

              </when>

              <when test="username !='' and username != null">

                  and username=#{username}

              </when>

              <otherwise>

                  and sex=#{sex}

              </otherwise>

          </choose>

      </where>

  </select>

  也就是说,这里我们有三个条件,id,username,sex,只能选择一个作为查询条件

    如果 id 不为空,那么查询语句为:select * from user where  id=?

    如果 id 为空,那么看username 是否为空,如果不为空,那么语句为 select * from user where  username=?;

          如果 username 为空,那么查询语句为 select * from user where sex=?

  

6.5、动态SQL:trim 语句

  trim标记是一个格式化的标记,可以完成set或者是where标记的功能

  ①、用 trim 改写上面第二点的 if+where 语句

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">

        select * from user

        <!-- <where>

            <if test="username != null">

               username=#{username}

            </if>

             

            <if test="username != null">

               and sex=#{sex}

            </if>

        </where>  -->

        <trim prefix="where" prefixOverrides="and | or">

            <if test="username != null">

               and username=#{username}

            </if>

            <if test="sex != null">

               and sex=#{sex}

            </if>

        </trim>

    </select>

  prefix:前缀      

  prefixoverride:去掉第一个and或者是or

 

  ②、用 trim 改写上面第三点的 if+set 语句

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

<!-- 根据 id 更新 user 表的数据 -->

    <update id="updateUserById" parameterType="com.ys.po.User">

        update user u

            <!-- <set>

                <if test="username != null and username != ''">

                    u.username = #{username},

                </if>

                <if test="sex != null and sex != ''">

                    u.sex = #{sex}

                </if>

            </set> -->

            <trim prefix="set" suffixOverrides=",">

                <if test="username != null and username != ''">

                    u.username = #{username},

                </if>

                <if test="sex != null and sex != ''">

                    u.sex = #{sex},

                </if>

            </trim>

         

         where id=#{id}

    </update>

  suffix:后缀  

  suffixoverride:去掉最后一个逗号(也可以是其他的标记,就像是上面前缀中的and一样)

6.6、动态SQL: SQL 片段

  有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

  比如:假如我们需要经常根据用户名和性别来进行联合查询,那么我们就把这个代码抽取出来,如下:

1

2

3

4

5

6

7

8

9

<!-- 定义 sql 片段 -->

<sql id="selectUserByUserNameAndSexSQL">

    <if test="username != null and username != ''">

        AND username = #{username}

    </if>

    <if test="sex != null and sex != ''">

        AND sex = #{sex}

    </if>

</sql>

  引用 sql 片段

1

2

3

4

5

6

7

8

<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">

    select * from user

    <trim prefix="where" prefixOverrides="and | or">

        <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->

        <include refid="selectUserByUserNameAndSexSQL"></include>

        <!-- 在这里还可以引用其他的 sql 片段 -->

    </trim>

</select>

  注意:①、最好基于 单表来定义 sql 片段,提高片段的可重用性

     ②、在 sql 片段中不要包括 where 

6.7、动态SQL: foreach 语句

  需求:我们需要查询 user 表中 id 分别为1,2,3的用户

  sql语句:select * from user where id=1 or id=2 or id=3

       select * from user where id in (1,2,3)

 

①、建立一个 UserVo 类,里面封装一个 List<Integer> ids 的属性

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

package com.ys.vo;

 

import java.util.List;

 

public class UserVo {

    //封装多个用户的id

    private List<Integer> ids;

 

    public List<Integer> getIds() {

        return ids;

    }

 

    public void setIds(List<Integer> ids) {

        this.ids = ids;

    }

 

}  

 

②、我们用 foreach 来改写 select * from user where id=1 or id=2 or id=3

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.ys.po.User">

    select * from user

    <where>

        <!--

            collection:指定输入对象中的集合属性

            item:每次遍历生成的对象

            open:开始遍历时的拼接字符串

            close:结束时拼接的字符串

            separator:遍历对象之间需要拼接的字符串

            select * from user where 1=1 and (id=1 or id=2 or id=3)

          -->

        <foreach collection="ids" item="id" open="and (" close=")" separator="or">

            id=#{id}

        </foreach>

    </where>

</select>

  测试:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//根据id集合查询user表数据

@Test

public void testSelectUserByListId(){

    String statement = "com.ys.po.userMapper.selectUserByListId";

    UserVo uv = new UserVo();

    List<Integer> ids = new ArrayList<>();

    ids.add(1);

    ids.add(2);

    ids.add(3);

    uv.setIds(ids);

    List<User> listUser = session.selectList(statement, uv);

    for(User u : listUser){

        System.out.println(u);

    }

    session.close();

}

  

③、我们用 foreach 来改写 select * from user where id in (1,2,3)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.ys.po.User">

        select * from user 

        <where>

            <!--

                collection:指定输入对象中的集合属性

                item:每次遍历生成的对象

                open:开始遍历时的拼接字符串

                close:结束时拼接的字符串

                separator:遍历对象之间需要拼接的字符串

                select * from user where 1=1 and id in (1,2,3)

              -->

            <foreach collection="ids" item="id" open="and id in (" close=") " separator=",">

                #{id}

            </foreach>

        </where>

    </select>

 如果遍历数组或者list,需要添加fi标签判断是否有内容,这里需要使用array和list来代表数组和list,用array.length和list.size来代表长度。因为mybatis中动态SQL使用的是OGNL,而OGNL中表达式数组用array表示,list亦是如此。。

7、关联关系查询

7.1、一对一关联

   1、提出需求

  根据班级id查询班级信息(带老师的信息)

   2、创建表和数据

  创建一张教师表和班级表,这里我们假设一个老师只负责教一个班,那么老师和班级之间的关系就是一种一对一的关系。

 1 CREATE TABLE teacher(
 2     t_id INT PRIMARY KEY AUTO_INCREMENT, 
 3     t_name VARCHAR(20)
 4 );
 5 CREATE TABLE class(
 6     c_id INT PRIMARY KEY AUTO_INCREMENT, 
 7     c_name VARCHAR(20), 
 8     teacher_id INT
 9 );
10 ALTER TABLE class ADD CONSTRAINT fk_teacher_id FOREIGN KEY (teacher_id) REFERENCES teacher(t_id);    
11 
12 INSERT INTO teacher(t_name) VALUES('teacher1');
13 INSERT INTO teacher(t_name) VALUES('teacher2');
14 
15 INSERT INTO class(c_name, teacher_id) VALUES('class_a', 1);
16 INSERT INTO class(c_name, teacher_id) VALUES('class_b', 2);

  表之间的关系如下:

  

3、定义实体类

  1、Teacher类,Teacher类是teacher表对应的实体类。

 1 package me.gacl.domain;
 2 
 3 /**
 4  * @author gacl
 5  * 定义teacher表对应的实体类
 6  */
 7 public class Teacher {
 8 
 9     //定义实体类的属性,与teacher表中的字段对应
10     private int id;            //id===>t_id
11     private String name;    //name===>t_name
12 
13     public int getId() {
14         return id;
15     }
16 
17     public void setId(int id) {
18         this.id = id;
19     }
20 
21     public String getName() {
22         return name;
23     }
24 
25     public void setName(String name) {
26         this.name = name;
27     }
28 
29     @Override
30     public String toString() {
31         return "Teacher [id=" + id + ", name=" + name + "]";
32     }
33 }

 

 2、Classes类,Classes类是class表对应的实体类

 1 package me.gacl.domain;
 2 
 3 /**
 4  * @author gacl
 5  * 定义class表对应的实体类
 6  */
 7 public class Classes {
 8 
 9     //定义实体类的属性,与class表中的字段对应
10     private int id;            //id===>c_id
11     private String name;    //name===>c_name
12     
13     /**
14      * class表中有一个teacher_id字段,所以在Classes类中定义一个teacher属性,
15      * 用于维护teacher和class之间的一对一关系,通过这个teacher属性就可以知道这个班级是由哪个老师负责的
16      */
17     private Teacher teacher;
18 
19     public int getId() {
20         return id;
21     }
22 
23     public void setId(int id) {
24         this.id = id;
25     }
26 
27     public String getName() {
28         return name;
29     }
30 
31     public void setName(String name) {
32         this.name = name;
33     }
34 
35     public Teacher getTeacher() {
36         return teacher;
37     }
38 
39     public void setTeacher(Teacher teacher) {
40         this.teacher = teacher;
41     }
42 
43     @Override
44     public String toString() {
45         return "Classes [id=" + id + ", name=" + name + ", teacher=" + teacher+ "]";
46     }
47 }

4、定义sql映射文件classMapper.xml

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 3 <!-- 为这个mapper指定一个唯一的namespace,namespace的值习惯上设置成包名+sql映射文件名,这样就能够保证namespace的值是唯一的
 4 例如namespace="me.gacl.mapping.classMapper"就是me.gacl.mapping(包名)+classMapper(classMapper.xml文件去除后缀)
 5  -->
 6 <mapper namespace="me.gacl.mapping.classMapper">
 7 
 8     <!-- 
 9         根据班级id查询班级信息(带老师的信息)
10         ##1. 联表查询
11         SELECT * FROM class c,teacher t WHERE c.teacher_id=t.t_id AND c.c_id=1;
12         
13         ##2. 执行两次查询
14         SELECT * FROM class WHERE c_id=1;  //teacher_id=1
15         SELECT * FROM teacher WHERE t_id=1;//使用上面得到的teacher_id
16      -->
17 
18     <!-- 
19     方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
20              封装联表查询的数据(去除重复的数据)
21         select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=1
22     -->
23     <select id="getClass" parameterType="int" resultMap="ClassResultMap">
24         select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id}
25     </select>
26     <!-- 使用resultMap映射实体类和字段之间的一一对应关系 -->
27     <resultMap type="me.gacl.domain.Classes" id="ClassResultMap">
28         <id property="id" column="c_id"/>
29         <result property="name" column="c_name"/>
30         <association property="teacher" javaType="me.gacl.domain.Teacher">
31             <id property="id" column="t_id"/>
32             <result property="name" column="t_name"/>
33         </association>
34     </resultMap>
35     
36     <!-- 
37     方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型
38         SELECT * FROM class WHERE c_id=1;
39         SELECT * FROM teacher WHERE t_id=1   //1 是上一个查询得到的teacher_id的值
40     -->
41      <select id="getClass2" parameterType="int" resultMap="ClassResultMap2">
42         select * from class where c_id=#{id}
43      </select>
44      <!-- 使用resultMap映射实体类和字段之间的一一对应关系 -->
45      <resultMap type="me.gacl.domain.Classes" id="ClassResultMap2">
46         <id property="id" column="c_id"/>
47         <result property="name" column="c_name"/>
48         <association property="teacher" column="teacher_id" select="getTeacher"/>
49      </resultMap>
50      
51      <select id="getTeacher" parameterType="int" resultType="me.gacl.domain.Teacher">
52         SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}
53      </select>
54 
55 </mapper>

测试类和配置这里就不在多做说明了。。

 6、MyBatis一对一关联查询总结

  MyBatis中使用association标签来解决一对一的关联查询,association标签可用的属性如下:

  • property:对象属性的名称
  • javaType:对象属性的类型
  • column:所对应的外键字段名称
  • select:使用另一个查询封装的结果

7.2、一对多关联

1、提出需求

  根据classId查询对应的班级信息,包括学生,老师

2、创建表和数据

  在上面的一对一关联查询演示中,我们已经创建了班级表和教师表,因此这里再创建一张学生表

CREATE TABLE student(
    s_id INT PRIMARY KEY AUTO_INCREMENT, 
    s_name VARCHAR(20), 
    class_id INT
);
INSERT INTO student(s_name, class_id) VALUES('student_A', 1);
INSERT INTO student(s_name, class_id) VALUES('student_B', 1);
INSERT INTO student(s_name, class_id) VALUES('student_C', 1);
INSERT INTO student(s_name, class_id) VALUES('student_D', 2);
INSERT INTO student(s_name, class_id) VALUES('student_E', 2);
INSERT INTO student(s_name, class_id) VALUES('student_F', 2);

  

3、定义实体类

  1、Student类略,ID,name两个属性

  2、修改Classes类,添加一个List<Student> students属性,使用一个List<Student>集合属性表示班级拥有的学生,如下:

 1 package me.gacl.domain;
 2 
 3 import java.util.List;
 4 
 5 /**
 6  * @author gacl
 7  * 定义class表对应的实体类
 8  */
 9 public class Classes {
10 
11     //定义实体类的属性,与class表中的字段对应
12     private int id;            //id===>c_id
13     private String name;    //name===>c_name
14     
15     /**
16      * class表中有一个teacher_id字段,所以在Classes类中定义一个teacher属性,
17      * 用于维护teacher和class之间的一对一关系,通过这个teacher属性就可以知道这个班级是由哪个老师负责的
18      */
19     private Teacher teacher;
20     //使用一个List<Student>集合属性表示班级拥有的学生
21     private List<Student> students;
22     setter和getter和tostring略
60 }

4、修改sql映射文件classMapper.xml

  添加如下的SQL映射信息

<!-- 
        根据classId查询对应的班级信息,包括学生,老师
     -->
    <!-- 
    方式一: 嵌套结果: 使用嵌套结果映射来处理重复的联合结果的子集
    SELECT * FROM class c, teacher t,student s WHERE c.teacher_id=t.t_id AND c.C_id=s.class_id AND  c.c_id=1
     -->
    <select id="getClass3" parameterType="int" resultMap="ClassResultMap3">
        select * from class c, teacher t,student s where c.teacher_id=t.t_id and c.C_id=s.class_id and  c.c_id=#{id}
    </select>
    <resultMap type="me.gacl.domain.Classes" id="ClassResultMap3">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" column="teacher_id" javaType="me.gacl.domain.Teacher">
            <id property="id" column="t_id"/>
            <result property="name" column="t_name"/>
        </association>
        <!-- ofType指定students集合中的对象类型 -->
        <collection property="students" ofType="me.gacl.domain.Student">
            <id property="id" column="s_id"/>
            <result property="name" column="s_name"/>
        </collection>
    </resultMap>
    
    <!-- 
        方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型
            SELECT * FROM class WHERE c_id=1;
            SELECT * FROM teacher WHERE t_id=1   //1 是上一个查询得到的teacher_id的值
            SELECT * FROM student WHERE class_id=1  //1是第一个查询得到的c_id字段的值
     -->
     <select id="getClass4" parameterType="int" resultMap="ClassResultMap4">
        select * from class where c_id=#{id}
     </select>
     <resultMap type="me.gacl.domain.Classes" id="ClassResultMap4">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" column="teacher_id" javaType="me.gacl.domain.Teacher" select="getTeacher2"></association>
        <collection property="students" ofType="me.gacl.domain.Student" column="c_id" select="getStudent"></collection>
     </resultMap>
     
     <select id="getTeacher2" parameterType="int" resultType="me.gacl.domain.Teacher">
        SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}
     </select>
     
     <select id="getStudent" parameterType="int" resultType="me.gacl.domain.Student">
        SELECT s_id id, s_name name FROM student WHERE class_id=#{id}
     </select>

 MyBatis一对多关联查询总结

  MyBatis中使用collection标签来解决一对多的关联查询,ofType属性指定集合中元素的对象类型。

7.3、多对多查询


在Java Resources/src 的包 gler.mybatis.manytomany.model 下新建类 Student.java,一个学生具有 id、name、sex、age、courses(List<Course>)属性。学生和课程之间是多对多关系,一个学生可以选多门课。

Student.java 的代码如下:

package gler.mybatis.manytomany.model;

import java.util.List;

public class Student {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
    private List<Course> courses; 

    setter,getter,无参有参以及tostring略

}
再在包 gler.mybatis.manytomany.model 下新建类 Course.java,一个班级有 id,name,credit、students(List<Student>) 属性。课程和学生之间是多对多关系,一个课程可以由多个学生选。

Course.java 的代码如下:

package gler.mybatis.manytomany.model;

import java.util.List;

public class Course {
    private Integer id;
    private String name;
    private Integer credit;
    private List<Student> students;

    setter,getter,无参有参以及tostring略
最后在包 gler.mybatis.manytomany.model 下新建类 StudentCourseLink.java,用来描述学生和课程之间的关系,其包含 
student(Student)、course(Course)、date 属性。

package gler.mybatis.manytomany.model;

import java.util.Date;

public class StudentCourseLink {
    private Student student;
    private Course course;
    private Date date;

   setter,getter,无参有参以及tostring略

}

StudentMapper.xml 的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="gler.mybatis.manytomany.mapper.StudentMapper">
    <!-- 查询所有学生及他们的选择课程的信息 -->
    <select id="selectStudentCourse" resultMap="studentCourseMap">
        select
        s.*,c.* from
        tb_student s,tb_course c,tb_select_course sc
        where s.s_id=sc.sc_s_id
        and c.c_id=sc.sc_c_id
    </select>

    <!-- 根据学生id和课程id删除该学生该门课的选课情况 -->
    <delete id="deleteStudentCourseById" parameterType="StudentCourseLink">  
        delete from tb_select_course where sc_s_id=#{student.id} and sc_c_id=#{course.id}
    </delete> 

    <!-- resultMap:映射实体类和字段之间的一一对应的关系 -->
    <resultMap id="studentCourseMap" type="Student">
        <id property="id" column="s_id" />
        <result property="name" column="s_name" />
        <result property="sex" column="s_sex" />
        <result property="age" column="s_age" />
        <!-- 多对多关联映射:collection -->
        <collection property="courses" ofType="Course">
            <id property="id" column="c_id" />
            <result property="name" column="c_name" />
            <result property="credit" column="c_credit" />
        </collection>
    </resultMap>
</mapper>
在这里,采用的是集合的嵌套结果映射的方式,使用了 <collection.../> 元素映射多对多的关联关系。

无论是一对一,多对多还是一对多都可以去看本地动力节点老郭讲义mybatis文案。如有需要的可以到本人博客自行下载。。。

8、延迟加载

MyBatis 中的延迟加载,也称为懒加载,是指在进行关联查询时, 按照设置延迟规则推迟对关联对象的 select 查询。 延迟加载可以有效的减少数据库压力。需要注意的是, MyBatis 的延迟加载只是对关联对象的查询有迟延设置,对于主加载对象都是直接执行查询语句的。

8.1、关联对象加载时机

MyBatis 根据对关联对象查询的 select 语句的执行时机,分为三种类型:直接加载、侵入式延迟加载与深度延迟加载。

 

  •  直接加载: 执行完对主加载对象的 select 语句,马上执行对关联对象的 select 查询。
  •  侵入式延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情时,就会马上执行关联对象的 select 查询。 即对关联对象的查询执行,侵入到了主加载对象的详情访问中。也可以这样理解:将关联对象的详情侵入到了主加载对象的详情中,即将关联对象的详情作为主加载对象的详情的一部分出现了。
  •  深度延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的 select 查询。只有当真正访问关联对象的详情时,才会执行对关联对象的 select 查询。

需要注意的是, 延迟加载的应用要求,关联对象的查询与主加载对象的查询必须是分别进行的 select 语句,不能是使用多表连接所进行的 select 查询。 因为,多表连接查询,其实质是对一张表的查询,对由多个表连接后形成的一张表的查询。会一次性将多张表的所有信息查询出来。
MyBatis 中对于延迟加载设置,可以应用到一对一、一对多、多对一、多对多的所有关联关系查询中。
1.直接加载

在主配置文件的<properties/>与<typeAliases/>标签之间,添加<settings/>标签,用于完
成全局参数设置。

全局属性 lazyLoadingEnabled 的值只要设置为 false,那么,对于关联对象的查询,将采用直接加载。 即在查询过主加载对象后,会马上查询关联对象。lazyLoadingEnabled 的默认值为 false,即直接加载。

2.深度延迟加载

修改主配置文件的<settings/>,将延迟加载开关 lazyLoadingEnabled 开启(置为 true),将侵入式延迟加载开关 aggressiveLazyLoading 关闭(置为 false)。

3.侵入式延迟加载
修改主配置文件的<settings/>,将延迟加载开关 lazyLoadingEnabled 开启(置为 true),将侵入式延迟加载开关 aggressiveLazyLoading 也开启(置为 true,默认为 true)。

注意:启用了延时加载SQL语句一定要这样写:

其中老师和学生为一对一关系,老师和book为一对多关系,和上面一对多介绍中类似,可以比对。。

<!--第二种:嵌套查询,延迟加载专用-->
<select id="lazyLoadingSelectTeacherAll" resultMap="lazyLoading">
    select * from dao_teacher where tid=#{xxx}
</select>
<resultMap id="lazyLoading" type="com.shuai.common.dao_Teacher">
    <id column="tid" property="id"></id>
    <result column="tname" property="name"></result>
    <result column="tage" property="age"></result>
    <association property="student" javaType="com.shuai.common.dao_Student" select="lazyLoadingSelectStu" column="sid">

    </association>
    <collection property="book" ofType="com.shuai.common.dao_book" select="lazyLoadingSelectBook" column="tid">

    </collection>
</resultMap>

<select id="lazyLoadingSelectStu" resultType="com.shuai.common.dao_Student">
    select sid id,sname name,sage age,score core from dao_student where sid=#{xxx}
</select>
<select id="lazyLoadingSelectBook" resultType="com.shuai.common.dao_book">
    select bid id,bname name,bprice price from dao_book where tid=#{xxx}
</select>

9、查询缓存

查询缓存的使用,主要是为了提高查询访问速度。将用户对同一数据的重复查询过程简化,不再每次均从数据库查询获取结果数据,从而提高访问速度。
MyBatis 的查询缓存机制,根据缓存区的作用域(生命周期) 可划分为两种: 一级查询缓存与二级查询缓存。
 

9.1、一级查询缓存(只对select语句有效)

MyBatis 一级查询缓存是基于 org.apache.ibatis.cache.impl.PerpetualCache 类的 HashMap本地缓存,其作用域是 SqlSession。在同一个 SqlSession 中两次执行相同的 sql 查询语句,第一次执行完毕后,会将查询结果写入到缓存中,第二次会从缓存中直接获取数据,而不再到数据库中进行查询,从而提高查询效率。
当一个 SqlSession 结束后,该 SqlSession 中的一级查询缓存也就不存在了。 myBatis 默认一级查询缓存是开启状态,且不能关闭。

在测试类中修改代码,如下:

控制台如下:

执行完后,发现只执行了一次从 DB 中的查询,第二次的结果是直接输出的。说明,第二次是从 SqlSession 缓存中读取的。

  • 一级缓存缓存的是相同 Sql 映射 id 的查询结果,而非相同 Sql 语句的查询结果。 因为myBatis 内部对于查询缓存,无论是一级查询缓存还是二级查询缓存, 其底层均使用一个HashMap 实现: key 为 Sql 的 id 相关内容, value 为从数据库中查询出的结果。
  • 除非修改映射文件,重新添加一个select2方法,在dao接口中添加select2方法。但是此时并不会使用一级缓冲。
  • 增、删、改操作,无论是否进行提交 sqlSession.commit(),均会清空一级查询缓存,使查询再次从 DB 中 select。

9.2、二级缓冲(只对select有效)

myBatis 查询缓存的作用域是根据映射文件 mapper 的 namespace 划分的,相同namespace的 mapper查询数据存放在同一个缓存区域。不同 namespace下的数据互不干扰。Reyco 教你学 Java 之 SpringMVC4 框架技术无论是一级缓存还是二级缓存,都是按照 namespace 进行分别存放的。但一、二级缓存的不同之处在于, SqlSession 一旦关闭,则 SqlSession 中的数据将不存在,即一级缓存就不覆存在。而二级缓存的生命周期会与整个应用同步,与 SqlSession 是否关闭无关。使用二级缓存的目的,不是共享数据,因为 MyBatis 从缓存中读取数据的依据是 SQL 的id,而非查询出的对象。所以, 二级缓存中的数据不是为了在多个查询之间共享(所有查询中只要查询结果中存在该对象的,就直接从缓存中读取,这是对数据的共享, Hibernate 中的缓存就是为了共享,但 MyBaits 的不是),而是为了延长该查询结果的保存时间,提高系统性能。
myBatis 内置的二级缓存为 org.apache.ibatis.cache.impl.PerpetualCache。
 

如何使用:

  1. 要求查询结果所涉及到的实体类要实现 java.io.Serializable 接口。若该实体类存在父类,或其具有域属性,则父类与域属性类也要实现序列化接口。
  2. 在 mapper 映射文件的<mapper/>标签中添加<cache/>子标签。

为<cache/>标签添加一些相关属性设置,可以对二级缓存的运行性能进行控制。当然,若不指定设置,则均保持默认值。

 eviction:逐出策略。当二级缓存中的对象达到最大值时,就需要通过逐出策略将缓存中的对象移出缓存。默认为 LRU。常用的策略有:

        FIFO: First In First Out, 先进先出

        LRU: Least Recently Used,未被使用时间最长的

 flushInterval:刷新缓存的时间间隔,单位毫秒。这里的刷新缓存即清空缓存。一般不指

定,即当执行增删改时刷新缓存。

 readOnly:设置缓存中数据是否只读。 只读的缓存会给所有调用者返回缓存对象的相同

实例,因此这些对象不能被修改,这提供了很重要的性能优势。但读写的缓存会返回缓

存对象的拷贝。这会慢一些,但是安全,因此默认是 false。

 size:二级缓存中可以存放的最多对象个数。默认为 1024 个。

9.2.1、二级缓冲关闭

二级缓存默认为开启状态。 若要将其关闭,则需要进行相关设置。根据关闭的范围大小,可以分为全局关闭与局部关闭。
1.全局关闭

所谓全局关闭是指,整个应用的二级缓存全部关闭,所有查询均不使用二级缓存。全局开关设置在主配置文件的全局设置<settings/>中,该属性为 cacheEnabled,设置为 false,则关闭;设置为 true,则开启, 默认值为 true。 即二级缓存默认是开启的。

2.局部关闭

所谓局部关闭是指,整个应用的二级缓存是开启的,但只是针对于某个<select/>查询,不使用二级缓存。此时可以单独只关闭该<select/>标签的二级缓存。在该要关闭二级缓存的<select/>标签中,将其属性 useCache 设置为 false,即可关闭该查询的二级缓存。 该属性默认为 true,即每个<select/>查询的二级缓存默认是开启的。

9.2.2、二级缓存的使用原则

(1) 只能在一个命名空间下使用二级缓存
由于二级缓存中的数据是基于 namespace 的,即不同 namespace 中的数据互不干扰。在多个 namespace 中若均存在对同一个表的操作,那么这多个 namespace 中的数据可能就会出现不一致现象。
(2) 在单表上使用二级缓存
如果一个表与其它表有关联关系,那么就非常有可能存在多个 namespace 对同一数据的操作。而不同 namespace 中的数据互不干扰,所以有可能出现这多个 namespace 中的数据不一致现象。
(3) 查询多于修改时使用二级缓存
在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。
(4)ehcache 二级查询缓存:mybatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。

关于ehcache的使用可以观看:https://www.cnblogs.com/pwc1996/p/4839133.html

也可以观看老郭讲义,之前有提到。。

10、mybatis注解开发

mybatis 的注解,主要是用于替换映射文件。而映射文件中无非存放着增、删、改、查的 SQL 映射标签。所以, mybatis 注解,就是要替换映射文件中的 SQL 标签。
mybatis 官方文档中指出,若要真正想发挥 mybatis 功能,还是要用映射文件。即 mybatis官方并不建议通过注解方式来使用 mybatis。
1 @Insert
其 value 属性用于指定要执行的 insert 语句。
2 @SelectKey
用于替换 XML 中的<selectKey/>标签,用于返回新插入数据的 id 值。

  • statement:获取新插入记录主键值的 SQL 语句
  • keyProperty:获取的该主键值返回后初始化对象的哪个属性
  • resultType:返回值类型
  • before:指定主键的生成相对于 insert 语句的执行先后顺序,该属性不能省略

3 @Delete
其 value 属性用于指定要执行的 delete 语句。
4 @Update
其 value 属性用于指定要执行的 update 语句。
5 @Select
其 value 属性用于指定要执行的 select 语句。

添加注解后剩余操作:

  1. 由于 MyBatis 注解替换的是映射文件,所以这里就不需要映射文件了,将其直接删除。
  2. 由于没有了映射文件,所以主配置文件中不能使用<mapper/>注册 mapper 的位置了。需要使用<package/>标签。

使用嵌套查询:老师和学生是一对一关系,老师和book是一对多关系

public interface OneToMore_Annotation_Teacher {
    @Select("select * from dao_teacher where tid=#{tid}")
    @Results({@Result(column ="tid",property = "id",id=true),//id=true,表示为主键
              @Result(column = "tname",property = "name"),
              @Result(column = "tage",property = "age"),
              @Result(column = "sid",property = "student",one =@One(select = "com.shuai.mapper.OneToMore_Annotation_Book.selectStudent")),
              @Result(column = "tid",property = "book",many = @Many(select="com.shuai.mapper.OneToMore_Annotation_Book.selectBooks"))
    })
    dao_Teacher selectTeacherAll(@Param("tid") int tid);
}
public interface OneToMore_Annotation_Book {
    @Select("select bid id,bname name,bprice price from dao_book where tid=#{tid}")
    @Results({
            @Result(column = "bid",property = "id",id=true),
            @Result(column = "bname",property = "name"),
            @Result(column = "bprice",property = "price")
    })
    dao_book selectBooks(@Param("tid") int tid);

    @Select("select sid id,sname name,sage age,score core from dao_student where sid=#{sid}")
    @Results({
            @Result(column = "sid",property = "id",id=true),
            @Result(column = "sname",property = "name"),
            @Result(column = "sage",property = "age"),
            @Result(column = "score",property = "core")
    })
    dao_Student selectStudent(@Param("sid") int sid);
}

 

 

11、问题:

1:属性名与字段名不一致问题

第一种方式:

<mapper namespace="dao.ISomeDao">

<resultMap type="Country" id="countryMapper">

<id column="cid" property="id"/>

<result column="cname" property="name"/>

<result column="cage" property="age"/>

</resultMap>

<select id="selectCountryById" resultMap="countryMapper">

select cid,cname,mid,mname from country,minister

where cid=#{xxx} and cid=countryId

</select>

</mapper>

第二种方式:

虽然属性名称与表中字段名称不一致,但可以为查询结果的字段名称赋予别名,让别名

与实体 Bean 的属性名相同。这样框架也可以根据查询结果利用反射机制将对象创建。

在映射文件 mapper 中添加如下映射。注意,由于表的 score 字段名与 Student 类的属性

名同名,所以这里无需使用别名。

<select id="selectAllStudent" resultType="Student">

select tid id,tname name,tage age,score from student where tid=#{xxx}

</select>

2:获取新插入语句的ID

<insert id="doCreate" parameterType="Address" >  

    <selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER" >  

     SELECT @@IDENTITY  

    </selectKey>  

    INSERT INTO Address(UId,LinkName,Address,Phone,Remark,Time,ZipCode)  

    VALUES(#{uid},#{linkName},#{address},#{phone},#{remark},now(),#{zipCode})  

</insert>  

3:$与#的区别

  • 1 #是将传入的值当做字符串的形式,eg:select id,name,age from student where id =#{id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id ='1'.
  •  2 $是将传入的数据直接显示生成sql语句,eg:select id,name,age from student where id =${id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id = 1.
  •  3 使用#可以很大程度上防止sql注入。(语句的拼接)
  •  4 但是如果使用在order by 中就需要使用 $.
  •  5 在大多数情况下还是经常使用#,但在不同情况下必须使用$. 

我觉得#与的区别最大在于:#{} 传入值时,sql解析时,参数是带引号的,而的区别最大在于:#{} 传入值时,sql解析时,参数是带引号的,而{}穿入值,sql解析时,参数是不带引号的。

一 : 理解mybatis中 $与#

    在mybatis中的$与#都是在sql中动态的传入参数。

    eg:select id,name,age from student where name=#{name}  这个name是动态的,可变的。当你传入什么样的值,就会根据你传入的值执行sql语句。

二:使用$与#

   #{}: 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,一个 #{ } 被解析为一个参数占位符 。底层调用preparestatement方法,有效避免sql注入。

   ${}: 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换。底层调用statement方法

  name-->cy

 eg:  select id,name,age from student where name=#{name}   -- name='cy'

       select id,name,age from student where name=${name}    -- name=cy

4、多参数问题

第一种方案 :

DAO层的函数方法 

Public User selectUser(String name,String area);

对应的Mapper.xml  

<select id="selectUser" resultMap="BaseResultMap">

select * from user_user_t where user_name = #{0} and user_area=#{1}

</select>

其中,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。

第二种方案:

此方法采用Map传多参数.

Dao层的函数方法

Public User selectUser(Map paramMap);

对应的Mapper.xml

<select id=" selectUser" resultMap="BaseResultMap">

select * from user_user_t where user_name = #{userName,jdbcType=VARCHAR} and user_area=#{userArea,jdbcType=VARCHAR}

</select>

Service层调用

Private User xxxSelectUser(){

Map paramMap=new hashMap();

paramMap.put(“userName”,”对应具体的参数值”);

paramMap.put(“userArea”,”对应具体的参数值”);

User user=xxx. selectUser(paramMap);}

个人认为此方法不够直观,见到接口方法不能直接的知道要传的参数是什么。

第三种方案:

Dao层的函数方法

Public User selectUser(@param(“userName”)String name,@param(“userArea”)String area);

对应的Mapper.xml

<select id=" selectUser" resultMap="BaseResultMap">

select * from user_user_t where user_name = #{userName,jdbcType=VARCHAR} and user_area=#{userArea,jdbcType=VARCHAR}

</select>

个人觉得这种方法比较好,能让开发者看到dao层方法就知道该传什么样的参数,比较直观,个人推荐用此种方案。

 

 

https://blog.youkuaiyun.com/ma15732625261/article/details/81123349

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值