Mybatis

Mybatis是干嘛的

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

快速开始

  • 导入依赖包
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>

<!--mysql的驱动包,就是对JDK提供的jdbc的一组实现,由各大数据库厂商提供
	如果要操作其他数据库,就要导入其他厂商的驱动包
-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
</dependency>
  • 基于xml配置文件创建SqlSessionFactory实例。

    SqlSessionFactory是干嘛的?
    	SqlSessionFactory是用来创建SqlSession。而SqlSession实例提供了在数据库执行Sql命令的所需要的方法。也可以从SqlSession实例获取Mapper对象,Mapper对象就是实现了一组执行Sql命令的方法的接口对象。
    

    SqlSessionFactory由SqlSessionFacotryBuilder类创建。

    SqlSessionFactoryBuilder的的作用:
    	SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的Configuration实例来构建出SqlSessionFactory实例。
    

    实现代码:

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

    Mybatis的xml配置文件:

    <!--Mybatis的配置文件主要是为构建Sqlsession实例提供元数据。
    	而读取和装配的实现由SqlSessionFactoryBuilder对象完成。
    -->
    <configuration>
        <!--environment 元素体中包含了事务管理和连接池的配置
    		mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。
    	-->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <!--mappers用来绑定所有所有的mapper.xml文件的-->
        <mappers>
            <mapper resource="User.xml"/>
        </mappers>
    </configuration>
    

    Mybatis的java代码是配置实现:

    //类似<dataSource type="POOLED"> </ dataSource>标签
    DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
    //这个是配置事务的
    TransactionFactory transactionFactory = new JdbcTransactionFactory();
    //类似<environment id="development"></environment>标签
    Environment environment = new Environment("development", transactionFactory, dataSource);
    
    Configuration configuration = new Configuration(environment);
    /*这里的加载映射器,是通过类+注解实现的,避免使用xml,但是有局限性(无法使用嵌套映射)
    	这种方法只适用于简单sql语句。如果是附加语句还是得用xml配置。
    	鉴于这种情况,如果存在一个同名的xml配置文件——BlogMapper.xml--Mybatis会自动查找并且加载它。
    */
    configuration.addMapper(BlogMapper.class);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
    
  • 创建Mapper类和Entities类(或者叫做POJO)。

    • Entites类

      一个Entities类对应一张表,Entites类的域就是表里的字段,并且Entites类只有getter和setter方法以及构造器。

    • Mapper类

      Mapper类是一个接口,是提供对数据库的具体操作(查询、添加、删除一个对象),通常一个Entites类有一个Mapper类。

package cn.wangmyi.Entities;

public class User {
    private int userid;
    private String userName;
    private String userSex;
    private int age;
}

public interface UserMapper {
    List<User> getUserList();
    int userUpdate(User user);
    int userInsert(User user);
    int userDelete(User user);
}
  • 创建Mapper.xml文件

    一个Mapper.xml文件绑定一个Mapper接口。

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--
    命名空间的作用有两个:
    	1、一个是利用更长的全限定名来将不同的语句隔离开来,
    	2、同时也实现了你上面见到的接口绑定。
    	就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。
    
    命名解析:为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。
    	1、全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
    	2、短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。
    -->
    <mapper namespace="cn.wangmyi.mappers.UserMapper">
        <select id="getUserList" resultType="User">
            select * from user
        </select>
        <update id="userUpdate" parameterType="cn.wangmyi.Entities.User" >
            update mybatis.user
            set
                `userName` = '${userName}',
                `userSex` = '${userSex}',
                `age` = ${age}
            where `userid` = ${userid}
        </update>
        <insert id="userInsert" parameterType="cn.wangmyi.Entities.User">
            insert into `mybatis`.`user` (user.userName, user.userSex, user.age)
            values ('${userName}','${userSex}',${age})
        </insert>
        <delete id="userDelete" parameterType="cn.wangmyi.Entities.User">
            delete from mybatis.user where `userid` = ${userid}
        </delete>
    </mapper>
    
  • 从SqlSessionFactory对象中获取SqlSession对象, 并通过SqlSession获取Mapper对象或者直接执行Mapper方法。

    • 获取SqlSession对象

      SqlSession session = sqlSessionFactory.openSession();
      
    • 直接通过SqlSession对象执行Mapper方法

      //老式mybatis的使用方式
      try (SqlSession session = sqlSessionFactory.openSession()) {
        Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
      }
      
    • 通过SqlSession对象获取Mapper接口的实现类

      try (SqlSession session = sqlSessionFactory.openSession()) {
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        Blog blog = mapper.selectBlog(101);
      }
      

作用域和生命周期

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

对象生命周期和依赖注入框架:Spring和Mybtis整合(MyBatis-Spring )

依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。 

SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

映射器实例

映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。 映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 你的应用逻辑代码
}

Configuration配置

这节说的是Configuration的精细配置。

Properties

这个元素可以从外部引入Properties文件也可以设置属性的值,用于整个配置文件中的需要动态配置的属性值。

比如datasource元素:

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

优先级: 外部引用文件优先级大于子标签设置的优先级.

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

可以通过占位符指定过一个默认值,如果哪个要读取属性没有被配置那么就设置成默认值,但是要开启支持,因为Mybatis默认关闭这个功能。

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 启用默认值特性 -->
</properties>
<dataSource type="POOLED">
  <property name="username" value="${username:ut_user}"/>
</dataSource>

Setting(具体看文档)

主要的设置(具体的看官方文档):

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true|false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true|false false
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true|false False
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J|LOG4J|LOG4J2|JDK_LOGGING |COMMONS_LOGGING |STDOUT_LOGGING |NO_LOGGING 未设置

typeAliases

为一个类的全限定明去一个别名。仅用于XML配置文件,但是不适用于Mapper的namespace属性。

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

也可以指定一个报名,然后哪个包下的类用@Alias注解设置类的别名通过标签去搜索并加载这些别名。

@Alias("author")
public class Author {
    ...
}
<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

还有一些官网设置的常用别名,看官方文档。

TypeHandlers(具体看文档)

这个的功能就是,如果我们设置preparedStatemt预处理语句的话,我们要给prepearedStatement的占位符替换成值(这个值的类型必须符合数据库表的字段类型)。那么TypeHandler就会帮我们做这件事,同时它也会帮我们将结果集中的值处理成符合我们Java类型的值。

这个东西Mybatis已经做的很好了,这小节的重点是,我们要去设计一个我们自己的类型处理器,去处理那么Mybatis提供的默认处理器无法处理的数据类型。具体的看官方文档。

objectFactory(具体看文档)

每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。

environments

Mybatis可以创建多个环境。但是一个SqlSessionFactory实例只能选择一种环境,所以要连接多个不同的数据库就要创建多个SqlSessionFactory实例。

transactionManager

如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:

    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>
    

dataSource

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

  • 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

  • driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
  • url – 这是数据库的 JDBC URL 地址。
  • username – 登录数据库的用户名。
  • password – 登录数据库的密码。
  • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。
  • defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout() 的 API 文档以获取更多信息。

作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:

  • driver.encoding=UTF8

这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8encoding 属性给数据库驱动。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

  • poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10
  • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
  • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
  • poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
  • poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnectionspoolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5)
  • poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。
  • poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
  • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。

JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:

  • initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
  • data_source – 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。

和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给 InitialContext。比如:

  • env.encoding=UTF8

这就会在 InitialContext 实例化时往它的构造方法传递值为 UTF8encoding 属性。

你可以通过实现接口 org.apache.ibatis.datasource.DataSourceFactory 来使用第三方数据源实现:

public interface DataSourceFactory {
  void setProperties(Properties props);
  DataSource getDataSource();
}

org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父类来构建新的数据源适配器,比如下面这段插入 C3P0 数据源所必需的代码:

import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {

  public C3P0DataSourceFactory() {
    this.dataSource = new ComboPooledDataSource();
  }
}

为了令其工作,记得在配置文件中为每个希望 MyBatis 调用的 setter 方法增加对应的属性。 下面是一个可以连接至 PostgreSQL 数据库的例子:

<dataSource type="org.myproject.C3P0DataSourceFactory">
  <property name="driver" value="org.postgresql.Driver"/>
  <property name="url" value="jdbc:postgresql:mydb"/>
  <property name="username" value="postgres"/>
  <property name="password" value="root"/>
</dataSource>

mappers

这个会高数Mybatis去哪里找映射文件。

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

Mapper映射器的属性

  • select – 映射查询语句。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • sql – 可被其它语句引用的可重用语句块(太复杂了 我认为会导致代码的难以理解)

Select

属性 描述
id 就是要实现的接口的方法名字。
parameterType 实现接口的方法的参数类型。
resultType 返回结果的类型,就是会用返回的结果创建一个实体类(该resultType类)对象。
resultMap 放回结果集映射,起到一个中间的人的作用,就是将实体类中属性明映射成结果集中的字段名来取值,因为两个值不同的话就无法取出值。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。jdbc我们用的是statement,Preparedstatement。第二个(prepared可以放置sql注入)。
flushCache 刷新所有的缓存
以下是目前我不懂的属性的作用
属性 描述
useCache
timeOut
resultSet
databaseId
resultSet
resultOrdered
resultSets

insert,update,delete

属性 描述
useGeneratedKeys (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn (仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。

多行插入示例:

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

selectKey(设置随即插入主键)

属性 描述
keyProperty selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn 返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。
resultType 结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。
order 可以设置为 BEFOREAFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
statementType

随机插入主键代码示例:

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

结果映射

  • id & result

    • 作用:idresult 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。
    • id和result区别:id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。(这点不理解)
  • constructor

    • 作用:通过修改对象属性的方式,可以满足大多数的数据传输对象(Data Transfer Object, DTO)以及绝大部分领域模型的要求。但有些情况下你想使用不可变类。 一般来说,很少改变或基本不变的包含引用或数据的表,很适合使用不可变类。 构造方法注入允许你在初始化时为类设置属性的值,而不用暴露出公有方法

      上面那一段是说,mybatis实例化一个实体类对象是通过getter和setter方法给实体类对象初始化值的,然而有些实体类有些字段是没有getter和setter方法的,就是说,这个字段是不允许外界访问和修改的,所以我们在初始化这个对象字段的时候我们要调用构造器去初始化这些字段。
      
  • association

    association是初始化字段对象为类。就是说用查询语句的列值来初始化学生实体类的Teacher字段,为Student下的Teacher类的字段赋值。

    association的一些属性:

    • 已经明白的一些属性(红字)。image-20210707194012895

    • notNullColumn:当指定的列属性为空时不创建这个类对象。

    • ResultSet:关联多结果集(要与存储过程一起使用)

      属性 描述
      column 当使用多个结果集时,该属性指定结果集中用于与 foreignColumn 匹配的列(多个列名以逗号隔开),以识别关系中的父类型与子类型。
      foreignColumn 指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。
      resultSet 指定用于加载复杂类型的结果集名字。

    mybatis加载关联的两种方式:

    • 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。

       <!--associate可以嵌套collection,反之亦然-->
      <mapper namespace="cn.wangmyi.mappers.StudentMapper">
          <resultMap id="StudentMap" type="Student">
              <id property="userid" column="studentid"/>
              <result property="name" column="studentname"/>
              <result property="userSex" column="studentsex"/>
              <result property="age" column="age"/>
              <association property="teacher" javaType="cn.wangmyi.Entities.Teacher">
                  <id property="userid" column="teacherid"/>
                  <result property="name" column="teachername"/>
                  <result property="userSex" column="teacherSex"/>
                  <result property="age" column="age"/>
                  <collection property="Students" ofType="cn.wangmyi.Entities.Student"
                              javaType="ArrayList">
                      <id property="userid" column="studentid" />
                      <result property="name" column="studentName"/>
                      <result property="userSex" column="studentsex"/>
                      <result property="age" column="age" />
                  </collection>
              </association>
          </resultMap>
          <select id="getStudent" parameterType="int" resultMap="StudentMap">
              select * from mybatis.student s inner join mybatis.teacher t
              on s.teacherid = t.teacherid
              where s.studentid = #{id}
          </select>
      </mapper>
      
    • (不推荐使用)嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。

      这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。 概括地讲,N+1 查询问题是这样子的:

      • 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
      • 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。
      <mapper namespace="cn.wangmyi.mappers.StudentMapper">
          <resultMap id="StudentMap" type="Student">
              <id property="userid" column="studentid"/>
              <result property="name" column="studentname"/>
              <result property="userSex" column="studentsex"/>
              <result property="age" column="age"/>
              <association property="teacher" javaType="cn.wangmyi.Entities.Teacher" column="teacherid" select="teacher" />
          </resultMap>
          <resultMap id="teachermap" type="cn.wangmyi.Entities.Teacher">
              <constructor>
                  <idArg column="teacherid" javaType="_int" />
                  <arg column="TeacherName" javaType="String" />
                  <arg column="teacherSex" javaType="String" />
                  <arg column="age" javaType="_int" />
              </constructor>
              <collection property="Students" ofType="cn.wangmyi.Entities.Student" javaType="ArrayList">
                  <id property="userid" column="studentid" />
                  <result property="name" column="studentName"/>
                  <result property="userSex" column="studentsex"/>
                  <result property="age" column="age" />
              </collection>
          </resultMap>
          <select id="teacher" parameterType="int" resultMap="teachermap">
              select * from mybatis.teacher  t inner join mybatis.student s
              on t.teacherid = s.teacherid
              where t.teacherid = #{id}
          </select>
          <select id="getStudent" parameterType="int" resultMap="StudentMap">
              select * from mybatis.student s inner join mybatis.teacher t
              on s.teacherid = t.teacherid
              where s.studentid = #{id}
          </select>
      </mapper>
      
  • collection

    collection是初始化一个集合类,就比如说,一个老师要带好多学生,所以一个初始化一个老师类对象也要把他的所有学生给带进去.

    <resultMap id="teachermap" type="cn.wangmyi.Entities.Teacher">
        <constructor>
            <idArg column="teacherid" javaType="_int" />
            <arg column="TeacherName" javaType="String" />
            <arg column="teacherSex" javaType="String" />
            <arg column="age" javaType="_int" />
        </constructor>
        <collection property="Students" ofType="cn.wangmyi.Entities.Student" javaType="ArrayList">
            <id property="userid" column="studentid" />
            <result property="name" column="studentName"/>
            <result property="userSex" column="studentsex"/>
            <result property="age" column="age" />
        </collection>
    </resultMap>
    <select id="teacher" parameterType="int" resultMap="teachermap">
        select * from mybatis.teacher  t inner join mybatis.student s
        on t.teacherid = s.teacherid
        where t.teacherid = #{id}
    </select>
    

动态SQL

动态sql让我们更加轻松的拼接sql语句,不用再去为借位的逗号或者是子条件开头的and或者or烦恼。

  • if

    判断入参是不是我们想要的,如果是我们想要的就拼接语句
    
  • choose when otherwise(这个和switch的差不都,可以用多个if来替换)

  • where(重点)

    where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
    
    <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>
      </where>
    </select>
    
  • trim自定义where

    prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
    
  • set

    <update id="updateAuthorIfNecessary">
      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}
    </update>
    
  • foreach

    <!--
    你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。-->
    <select id="selectPostIn" resultType="domain.blog.Post">
      SELECT *
      FROM POST P
      WHERE ID in
      <foreach item="item" index="index" collection="list"
          open="(" separator="," close=")">
            #{item}
      </foreach>
    </select>
    

缓存

聊聊MyBatis缓存机制 - 美团技术团队 (meituan.com)

我的一些理解

1、mybatis怎么将我们传入方法的参数映射到我们的sql语句去的。

这个我觉得是mybatis把我们的方法中的参数和参数值,取出,放入一个Map对象,map对象中,通过mapper.xml文件中 的配置语句用#{}来取我们的方法中的参数。

2、mybatis怎么将结果映射到我们的的结果对象中。

这个涉及的问题就是我们的实体类的对象属性名和数据库中的列名不一致的情况,就是说,如果我们的查询的结果集中的字段名和我们的实体类对象的属性名字不一致的话,mybatis就无法将值存入我们的实体类的对象中去,则该对象的部分属性(无法映射的)就会为默认值。

首先,我认为返回结果集是通过实体类的构造函数创建的实体对象,此时,mybatis会取出构造函数的参数名,用做取值的键值,把值映射出来放入实体类的构造参数。调用构造参数生成一个对象。

posted @ 2021-10-05 20:18  _LittleBee  阅读(51)  评论(0编辑  收藏  举报