这篇文章上次修改于 864 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

mybatis整体架构

组件

MyBatis 的核心组件分为 4 个部分 :

SqlSessionFactoryBuilder(构造器):它会根据配置或者代码来生成 SqlSessionFactory,采用的是分步构建的 Builder 模式。

SqlSessionFactory(工厂接口):依靠它来生成 SqlSession , 使用的是工厂模式。

SqlSession (会话):  一个既可以发送 SQL 执行返回结果,也可以获取 Mapper 的接口。在现有的技术中, 一般我们会让其在业务逻辑代码中“消失”,而使用的是My Batis 提供的 SQLMapper接口编程技术,它能提高代码的可读性和可维护性 。

SQL  Mapper  (映射器): MyBatis 新设计存在的组件,它由一个 Java 接口和 XML文件(或注解〉构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果 。

创建Factory

1.xml

在 MyBatis 中的 XML 分为两类,

一类是基础配置文件,通常只有一个,主要 是配置一些最基本的上下文参数和运行环境 ;

另一类是映射文件,它可以配置映射关系、SQL 、 参数等信息

<?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>    
    <typeAliases><!-- 别名 -->
        <typeAlias  alias= "role" type= "com.learn.ssm.chapter3.pojo.Role" /> 
    </typeAliases>
    <!-- 数据库环境 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://ip:port/jsp"/>
                <property name="username" value="......"/>
                <property name="password" value="......"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 映射文件 -->
    <mappers>
<!--        <mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
    </mappers>
</configuration>

这样定义后,在 MyBatis 上下文中就可以使用别名去代替全限定名了。

元素的定义, 这里描述的是数据库。 它里面的元素是配置事务管理器,这里采用的是 MyBatis 的 JDBC 管理器方式。然后采用元素配置数据库,其中属性 type=”POOLED”代表采用 MyBatis 内部提供的连接池方式,最后定义一些关于 JDBC 的属性信息。

元素代表引入的那些映射器

从配置文件创建Factory

    public SqlSessionFactory getFactory() {
        String Factory_Xml = "mybatis-config.xml";;
        InputStream inputStream = null;
        SqlSessionFactory sqlSessionFactory = null;
        try
        {
            inputStream = Resources.getResourceAsStream(Factory_Xml);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e)
        {
            e.printStackTrace();
        }
        return sqlSessionFactory;
    }
    @Test
    void test1() {
        SqlSessionFactory factory = new mySqlSessionFactory().getFactory_FromXml();
        var session = factory.openSession();
        var con = session.getConnection();
        System.out.println("connection" + con);
    }

2.配合properties文件

3.1 正常上下文配置

jdbcUrl=jdbc:mysql://ip:3306
database=jsp
username=jsp
password=123456
driverClassName=com.mysql.jdbc.Driver
<?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="archdb.properties"/>
    <typeAliases><!-- 别名 -->
        <typeAlias alias="book" type="pojo.Book"/>
    </typeAliases>
    <environments default="development2">
        <environment id="development1">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driverClassName}"/>
                <property name="url" value="${jdbcUrl}/${database}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
        <environment id="development2">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driverClassName}"/>
                <property name="url" value="${jdbcUrl}/${database}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--        <mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
    </mappers>
</configuration>

3.2 properties文件不写敏感信息

inputStream = Resources.getResourceAsStream(Factory_Xml);
// properties 文件可以不写敏感信息,程序中动态添加
properties.load(new FileInputStream("archdb.properties"));
properties.setProperty("username", "ajian");
properties.setProperty("password", "123456");
// build方法用properties替换原先xml的dataBase配置
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, properties);    
return sqlSessionFactory;

映射器

映射器是 MyBatis 中最重要、最复杂的组件,它由一个接口和对应的 XML 文件(或 注解〉组成。它可以配置以下内容 :

    描述映射规则 
    提供 SQL 语句,并可以配置 SQL 参数类型、返回类型、缓存刷新等信息。
    配置缓存
    提供动态 SQL 

映射器的主要作用就是将 SQL 查询到的结果映射为一个 POJO,或者将 POJO 的数据 插入到数据库中 , 并定义一些关于缓存等的重要内容。

注意 , 开发只是一个接口 , 而不是一个实现类。接口不能直接运行 。 MyBatis 运用了动态代理技术使 得接口能运行起来

实现

我们有一个book的POJO 类

1.xml

<!-- mybatis-config.xml  -->
    <mappers>
       <mapper resource="pojo/BookMapper.xml"/>  //指定 使用的mapper.xml文件路径
    </mappers>


<!--在 pojo/BookMapper.xml  -->

<?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">
<mapper namespace="pojo.BookMapper">
    <select id="getBook" parameterType="int" resultType="pojo.Book">
        select number, name, language
        from books
        where number = #{number}
    </select>
</mapper>



<!--在 pojo/BookMapper.java  -->
public interface BookMapper {
    public Book getBook(int number);
}


<!-- test.java  -->
   Book book1 = (Book) session.selectOne("pojo.BookMapper.getBook", 1);
<mapper>元素中的属性 namespace 所对应的是一个接口的全限定名 , 于是 MyBatis 上下文就可以通过它找到对应的接口。

<select>元素表明这是一条查询语旬 , 而属性 id 标识了这条 SQL,属性
parameterType= "long” 说明传递给 SQL 的是一个 long型的参数 ,而 resultType=”role”
表示返回的是一个 role 类型的返回值。而 role 是之前配置文件 mybatis-config.xml
配置的别名,指代的是 com.pojo.Role

这条 SQL 中的#{id}表示传递进去的参数。

注意,我们并没有配置 SQL 执行后和 role 的对应关系 其实这里采用的是一种被称为自动映射的功能, MyBatis在默认情况下提供自动映射 , 只要 SQL 返回的列名能和 POJO 对应起来即可。这里 SQL 返回的列名 id 和 note 是可以和之前定义的POJO 的属性对应起来的 ,而表里的列 role_name通过 SQL别名的改写 ,使其成为 roleName,
也是和 POJO 对应起来的,所以此时 MyBatis 就可以把 SQL 查询的结果通过自动映射的功
能映射成为一个 POJO 。

2.注解

//  pojo.UserMapper.java
public interface UserMapper {
    @Select("select id, username, password from user where id = #{id}")
    public User getUser(int id);
}


// mybatis-config.xml
    <mappers>
        <mapper class="pojo.UserMapper"/>
    </mappers>
            
// test.java        
User user1 = (User) session.selectOne("pojo.UserMapper.getUser", 1);

使用SqlSession

SqlSessionFactory 是一个接口,在 MyBatis 中它存在两个实现类 :

   `SqlSessionManager`和`DefaultSqlSessionFactory` 。 
    一般而言,具体是由 `DefaultSqlSessionFactory` 去实现的,而 `SqlSessionManager`使用在多线程的环境中,它的具体实现依靠 `DefaultSqlSessionFactory`

SqlSessionFactory 唯一的作用就是生产 MyBatis 的核心接口对象 SqlSession,所以它的责任
是唯一的 。 我们往往会采用单例模式处理它

作用:

获取 Mapper 接口。
发送 SQL 给数据库。
控制数据库事务。

常规使用

//定义 SqlSession
SqlSession  sqlSession  = null; 
try { 
    //打开 SqlSession会话
    sqlSession  = SqlSessionFactory.openSession() ; 
    
    //some  code 
    
    sqlSession. commit() ; //提交事务
    
}  catch(Exception  ex) { 
    
    sqlSession.rollback();//回滚事务
} finally {
    
    //在finally语句中确保资源被顺利关闭
    if  ( sqlSession != null) { 
        sqlSession.close(); 
    }
}

由于 SqlSession 的获取 Mapper 接口和发送 SQL 的功能需要先实现映射器的功能,而
映射器接口也可以实现发送 SQL 的功能

发送sql

用 Mapper接口发送 SQL (好)

    @Test
    void test2() {
        SqlSessionFactory factory = new mySqlSessionFactory().getFactory_FromXml();
        SqlSession session = factory.openSession();
        // 提供mapper的class 名,获取mapper接口对象
        BookMapper bookmapper = session.getMapper(BookMapper.class);
        // 用接口的方法获取pojo
        Book book1 = bookmapper.getBook(1);
        System.out.println(book1);
    }

生命周期

SqlSessionFactoryBuilder

目的是为了创建SqlSessionFactoy,创建完成后销毁

SqlSessionFactory

可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象,需要一直存在,以单例模式创建

SqlSession

它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory

try...catch .. . finally ...语句来保证其正确关闭

Mapper

生命周期应该小于等于 Sq!Session 的生命周期

test

    @Test
    void test2() {
        SqlSessionFactory factory = new mySqlSessionFactory().getFactory_FromXml();
        SqlSession session = factory.openSession();
        BookMapper bookmapper = session.getMapper(BookMapper.class);

        //select
        Book book1 = bookmapper.getBook(1);
        System.out.println(book1);

        //insert
        Book book2 = new Book();
        book2.setName("abc");
        book2.setLanguage("chinese");
        book2.setNumber(2);
        bookmapper.insertBook(book2);

        Book book3 = new Book();
        book3.setName("jsp");
        book3.setLanguage("chinese");
        book3.setNumber(3);
        bookmapper.insertBook(book3);

        //update
        book1.setLanguage("english");
        bookmapper.updateBook(book1);

        //delete
        bookmapper.deleteBook(2);

        session.commit();
    }
}

BookMapper.xml

<mapper namespace="pojo.book.BookMapper">
    <select id="getBook" parameterType="int" resultType="pojo.book.Book">
        select number, name, language
        from books
        where number = #{number}
    </select>
    <insert id="insertBook" parameterType="pojo.book.Book">
        insert into books (name, number, language)
        values (#{name}, #{number}, #{language})
    </insert>
    <update id="updateBook" parameterType="pojo.book.Book">
        update books
        set name=#{name},
            language=#{language}
        where number = #{number}
    </update>
    <delete id="deleteBook" parameterType="int">
        delete
        from books
        where number = #{number}
    </delete>
</mapper>

BookMapper

public interface BookMapper {
    public Book getBook(int number);
    public int insertBook(Book book);
    public int deleteBook(int number);
    public int updateBook(Book book);
}

详细配置

Configure

seetings

mybatis-配置

<settings>
    <setting name="key" value="value"/>
</settings>

类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

<typeAliases>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
    
    //每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。
      <package name="domain.blog"/>
    //@Alias("author")
    //public class Author {
    //    ...
    //}
    
</typeAliases>

类型处理器(typeHandlers)

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。

typeHandler 中,分为 jdbcTypejavaType , 其中 jdbcType 用于定义数据库类型,而 javaType 用于定义 Java 类型,那么 typeHandler 的作用就是承担 jdbcType javaType 之间的相互转换

自定义枚举或者特殊数据类型 需要自定义handler来处理

枚举

在绝大多数情况下 , typeHandler 因为枚举而使用, MyBatis 已经定义了两个类作为枚举类型的支持,这两个类分别是 :

• EnumOrdinalTypeHandler 。 // 表存 枚举值(0,1,2)

• EnumTypeHandler 。 // 表存字面量

Blob文件

byte[] -----> blob

可以使用系统注册的 typeHandle -----> BlobTypeHandler 来转换

为了减轻jvm的压力,一般采用文件流的形式,将 byte[] 类型转为 InputStream的形式, Mybatis 使用 BloblnputStream TypeHandler为你转换结果

但实际最好用路径存文件,不要用 数据库存纯文件信息

environments

在 MyBatis 中,运行环境主要的作用是配置数据库信息

他下面分为两个可配置元素 : 事物管理器transactionmanager 和 数据源 dataSource

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

如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例

指定环境创建Factory,不写则加载默认环境

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
<environments default="development1"><!--      配置环境-->
        <environment id="development1"><!--    环境变量           -->
            <transactionManager type="JDBC"/><!--           事务管理器     -->
            <dataSource type="POOLED"><!--           数据源     -->
                <property name="driver" value="${driverClassName}"/>
                <property name="url" value="${jdbcUrl}/${database}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
事物管理器 transactionManager

transactionManager 提供了两个实现类( JdbcTransaction , ManagedTransaction ),它需要实现接口 Transaction

于是它对应着两种工厂 : JdbcTransactionF actory 和 ManagedTransactionFactory , 这个工
厂需要实现 TransactionFactory 接口,通过它们会生成对应的 Transaction对象

<transactionManager  type= " JDBC " />
<transactionManager  type=" MANAGED " />        

JDBC 使用 JdbcTransactionFactory 生成的 JdbcTransaction 对象实现。它是以 JDBC的方式对数据库的提交和回滚进行操作。

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

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

dataSource数据源

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

UNPOOLED

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

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

数据库厂商标识(databaseIdProvider)

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:

<databaseIdProvider type="DB_VENDOR" />

databaseIdProvider 对应的 DB_VENDOR 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName() 返回的字符串。

改造Mapper.xml,添加厂商id

<select  id= "getRole" parameterType= " long"  resultType= "role" databaseId="oracle " >
select  id,  role  name  as  roleName, note  from  t role  where  id = # {id} 
</select>

我们知道使用多数据库 SQL 时需要配置 databaseldProvidertype 的属性。当 databaseldProvidetype 属性被配置时,系统会优先取到和数据库配置一致的 SQL 。

如果没有,则取没有 databaseId 的 SQL , 可以把它当作默认值。如果还是取不到, 则会抛出异常,说明无法匹配到对应的 SQL 。

config.mapper

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。

但首先,我们需要告诉 MyBatis 到哪里去找到这些语句

最好的办法是直接告诉 MyBatis 到哪里去找映射文件。

Mapper

它由一个接口加上 XML 文件(或者注解)组成

在映射器中可以配置参数、各类的 SQL语句、存储过程、缓存、级联等复杂的内容,并且通过简易的映射规则映射到指定的 POJO 或者其他对象上,映射器能有效消除 JDBC底层的代码。

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

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

select

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</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。
resultSetTypeFORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。
自动映射 驼峰映射

在 settting 元素中有两个可以配置的选项

autoMappingBehaviormapUnderscoreToCamel Case

它们是控制自动映射和驼峰映射的开关。

一般而言,自动映射会使用得多一些 ,因为可以通过 SQL 别名机制处理一些细节,比较灵活,而驼峰映射则要求比较严苛,所以在实际中应用不算太广 。

autoMappingBehavior选项的取值范围是 :

• NONE , 不进行自动映射。
• PARTIAL,默认值,只对没有嵌套结果集进行自动映射。
• FULL,对所有的结果集进行自动映射,包括嵌套结果集。

在默认情况下,使用默认的 PARTIAL 级别就可以了。

如果编写的SQL 列名和属性名保持一致,那么它就会形成自动映射,

如果系统都严格按照驼峰命名法

db列名 role_name pojo属性名 roleName

比如可能有些字段 有主表 和 从表关联 的 级联, 又如 typeHandler 的复杂转换规则,此时 resultType 元素是无法满足这些需求的。如果需要更为强大的映射规则,则需要考虑使用 resultMap

缺点 如果值为 null 必须指定 类型处理器

或者用别名来 利用 自动映射

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>
多个传参
注解

@Param ( org.apache. atis .annotations.Param)

public List<Role>  findRolesByAnnotation
    (@Param ("roleName")String rolename , @Param ("note" ) String note)    
;
<select  id="findRolesByAnnotation" resultType="role" >
    
        select id , role  name  as roleName , note  from  t_role where role  name  like  concat ('%',#{ roleName } ,'%') and  note  like  concat ( '%', #{ note } ,'%')
    
</select> 

此时并不需要给出 parameterType 属性,让 MyBatis 自动探索便可以了

如果 SQL 很 复杂,拥有大于 10 个参数,那么接口方法的参数个数就多了,使用起来就很不容易

javaBean
  1. 将参数组合成一个javabean
  1. 接口方法参数定义为此 bean对象

    public List<Role> findRolesByBean(RoleParams roleParam)
  2. mapper.xml 中填写 select 语句

    <select  id="findRolesByAnnotation" resultType="role"  parameterType= " bean.RoleParams  ">
            select id , role  name  as roleName , note  from  t_role where role  name  like  concat ('%',#{ roleName } ,'%') and  note  like  concat ( '%', #{ note } ,'%')
    </select> 

    4.使用时构建bean对象填充参数,调用接口方法

ex :混合使用 ( bean,分页器 )

分页 RowBounds

RowBounds 中 offset 属性是偏移量,即从第几行开始读取记录。 limit 是限制条数

// interface
public List<Role> findByRowBounds(@Param("roleName")String rolename, @Param ("note") String note , RowBounds rowBounds) ;

​ mapper 中 mybatis 自动识别 RowBounds 对象

 RowBounds 分页的原理是执行 SQL 的查 询后,按照偏移量和限制条数返回查询结果,

​ 所以对于大量的数据查询,它的性能并不佳, 此时可以通过分页插件去处理,

insert update delete

属性描述
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。
keyProperty(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
主键

取得自增主键

JDBC 中的 Statement 对象在执行插入的 SQL 后,可以通过 getGeneratedKeys 方法获得 数据库生成的主键(需要数据库驱动支持) ,这样便能达到获取主键的功能。

insett 语句中有一个开关属性 useGeneratedKeys,用来控制是否打开这个功能,它的默认值为false 。

当打开了这个开关,还要配置其属性 keyPrope 即或 keyColumn , 告诉系统把生成的主键放入哪个属性中,如果存在多个主键,就要用逗号(,)将它们隔开 。

<insert id="insertRole"  parameterType="role"
useGeneratedKeys="true " keyProperty="id" >
insert into t_role(role name, note) values( #{roleName} , #{note} )
</insert>
<!--useGeneratedKeys="true" 打开了自增主键开关  ,keyProperty="id" 将主键赋值给pojo的id字段 -->

主键自定义生成

<insert id= "insertRole" parameterType= "role">
        <selectKey keyProperty= "id" result_Type= "long" order= "BEFORE" >
          select if (max(id) =null, 1, max(id) + 3) from t_role 
        </selectKey>
    insert into t role(id, role name , note) values(#{id} , #{roleName}, #{note}) 
</insert>

定义了 selectKey 元素,它的 keyPrope 即指定了采用哪个属性作为 POJO 的主键

resultType 告诉 MyBatis 将返回一个 long 型的结果集,而 order 设置为 BEFORE,说明它将于当前定义的 SQL 前执行。通过这样就可以自定义主键的规则,可见 MyBatis 十分灵活。这里的 order 配置为 BEFORE, 说明它会在插入之前会先执行生成主键的 SQL, 然后插入数据。如果有一些特殊需要,可以把它设置为 AFTER,比如一些插入语句内部可
能有嵌入索引调用,这样它就会在插入语句之后执行了。

SQL元素(预编写语句)

sql 元素的作用在于可以定义一条 SQL 的一部分,方便后面的 SQL 引用它,比如最典 型的列名。 通常情况下要在 select 、insert 等语句中反复编写它们,特别是那些字段较多的表更是如此

<!-- mapper.xml-->
<sql id="roleCols" >
    id,name,language
</sql>

<select id=”getRole ” parameterType=" long" resultMap=” roleMap ” >
        select    <include refid="roleCols" />     from t_role where id=#{id}       
</select>

并且可以定义变量

image-20211207001546403

在 include 元素中定义了 一个命名为 alias 的变量,其值是 SQL 中表 t_role 的别名 ,然后 sql 元素就能够使用这个变量名了。

字符串替换 $ {columns}

比如为 了减缓数据库表的压力 , 有些企业会将一张很大 的数据库表按年份拆分,比如购买记录表( t_purchase records ) 。 现实中由于记录 比较多 , 可能为了方便按年份拆分为 t_purchase_records 2016 、t_purchase_records_ 2017 、
t_purchase_reco时s_2018 等,这时往往需要构建动态表名

构建动态列名常常要传递类似于字符串的 columns=” coll , col2, col3 ... ”给 SQL ,让其组装成为 SQL 语句。

可以写成 select $ {columns}from t_tablename ,这样 MyBatis 就不会转译 columns ,而不是作为 SQL 的参数进行设置了,而变为直出,这句 SQL 就会变为 select coll , col2, col3 ... from t_tablename 。

ResultMap

ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

Pojo存储结果集

image-20211209100628809

在学习了上面的知识后,你会发现上面的例子没有一个需要显式配置 ResultMap,这就是 ResultMap 的优秀之处——你完全可以不用显式地配置它们。 虽然上面的例子不用显式配置 ResultMap。 但为了讲解,我们来看看如果在刚刚的示例中,显式使用外部的 resultMap 会怎样,这也是解决列名不匹配的另外一种方式。

resultMap 元素的属性 id 代表这个 resultMap 的标识, type 代表着需要映射的 POJO,
这里可以使用 MyBatis 定义好的类的别名,也可以使用自定义的类的全限定名。

在映射关系中 , id 元素表示这个对象的主键, propety 代表着 POJO 的属性名称, column 表示数据库 SQL 的列名,于是 POJO 就和数据库 SQL 的结果一一对应起来了

为主键 为普通列

pojo 的属性 为表列名

<resultMap id="roleResultMap" type"com.Role" >
                <id property= "id" column = "id" />
                <result property="roleName"  column= "role_name " />
                <result property=” note ” column=” note ”/>
</resultMap>


//然后在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:
<select parameterType=” long ” id=”getRole ” resultMap = ” roleResultMap”>
        select id , role name, note from t role where id =#{id} 
</select>

image-20211209102328905

构造器

constructor 元素用于配置构造方法。 一个 POJO 没有无参构造方法 可以使用 constructor 进行配置。

 idArg 标识主键 


<resultMap ...... >
    <constructor >
        <idArg column="id" javaType="int"/>
        <arg column="role_name" javaType= "string" />
    </constructor>
</resultMap>

image-20211207003553367

级联

一个角色可能有多个用户,这就是一对多的级联:

还有一对一 的级联, 比如身份证和公民是一对一 的关系。

在 MyBatis 中还有一种被称为鉴别器的级联,它是一种可以选择具体实现类的级联,比如要查找雇员及其体检表的信息 ,但是雇员有性别之分,而根据性别的不同,其体检表的项目也会不一样,那么体检表就应该分为男性和女性两种,从而根据雇员 性别区分关联。

级联不是必须的,级联的好处是获取关联数据十分便捷,但是级联过多会增加系统的
复杂度,同时降低系统的性能,此增彼减,所以当级联的层级超过 3 层时,就不要考虑使
用级联了,因为这样会造成多个对象的关联,导致系统的精合、复杂和难以维护

My Batis 的级联分为 3 种。

·鉴别器( discriminator ):它是一个根据某些条件决定采用具体实现类级联的方案,
比如体检表要根据性别去区分。

·一对一( association ):比如学生证和学生就是一种一对一的级联,雇员和工牌表也
是一种一对一 的级联。

·一对多( collection ):比如班主任和学生就是一种一对多的级联。

association 一对一

你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:

  • 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
  • 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。

java代码中 pojo1 的一个 属性为 pojo2

但是表中 table1 的 某个列 为 table2 的主键 id

已知

class EmployeeTask{
   id , empid,
    Task,   
    name ....
}

class Task{
    ..........
}
//-------------------------------------------------------
table1       id,empid,id_2,name
    
table2       id_2, xxx 
select 嵌套查询

查询填充pojo1时 , 若其中一个元素 需要再次查询, 使用 association 关联

<resultMap   type="EmployeeTask" id="EmployeeTaskMap">
    
<id column= ” id” property= ” id” />
<result column= ” emp_id” property= ” empid” / >
<result column= ” task name ” property= ” taskName ” / >
<result column= ” note ” property= ” note ” />
    
<association property="task"  column="task_id"   select="selectid2"/>
// 此时  第一次查询出来的列结果为 task_id  , 本意为 Task的主键   
//  但 pojo1不存 pojo2的主键id ,而是直接对应的 pojo2 ,
//  所以指定 association 指定 两个 pojo的关系 , 将第一次查询结果 task_id 作为 第二次查询的参数
//  property 填写为 pojo2 , 并且指定 select的id 用 task_id为参数 调用 select填充 pojo2
    
    
 </resultMap>
    
 <select id="id1">
    </select>
         
 <select id="id2">
    </select>

其它所有的属性将会被自动加载,只要它们的列名和属性名相匹配。

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

  • 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
  • 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。

这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。

关联结果的嵌套映射
<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>
//  注意查询中的连接,以及为确保结果能够拥有唯一且清晰的名字,我们设置的别名。 这使得进行映射非常简单。现在我们可以映射这个结果:
//  使用 join 并给 所有结果设置别名 


// 在pojo1 的 resultmap 映射规则中 调用 pojo2 的 resultmap id
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
    
    // column 为pojo2的主键 
  <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>

</resultMap>

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

使用 join一次查找所有的列 ,用多个 resultmap来 填充pojo

上面的示例使用了外部的结果映射元素来映射关联。这使得 Author 的结果映射可以被重用

属性描述
resultMap结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 有部分数据是重复的。 为了将结果集正确地映射到嵌套的对象树中, MyBatis 允许你“串联”结果映射,以便解决嵌套结果集的问题。使用嵌套结果映射的一个例子在表格以后。
columnPrefix当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。 详细说明请参考后面的例子。
notNullColumn默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。
autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 selectresultMap 元素使用。默认值:未设置(unset)。
resultSet
collection 一对多

img

collection 几乎和 association 一样

你会注意到有一个新的 “ofType” 属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来

<resultMap id="deptMap1" type="Dept">
    <id column="dept_deptno" property="deptno"/>
    <result column="dname" property="dname"/>
    <result column="loc" property="loc"/>
    
    <!-- column 属性对应来自一方(一对多的一)表主键的字段名 -->
    <collection property="employeeList" ofType="Employee" column="dept_deptno">
        <id column="emp_no" property="empNo"/>
        <result column="e_name" property="eName"/>
    </collection>
    
     <!-- 又比如 -->
     <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
    
    
</resultMap>
鉴别器

对于因为 列值 不同 ,而映射关系不同

使用 鉴别器discriminator 和 case 语句 指定 resultmap

<resultMap id="vehicleResult" type="Vehicle">
  <id property="id" column="id" />
  <result property="vin" column="vin"/>
  <result property="year" column="year"/>
  <result property="make" column="make"/>
  <result property="model" column="model"/>
  <result property="color" column="color"/>
    
         <!-- 鉴别器  colunmn 指定被比较的列 -->
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultMap="carResult"/>
    <case value="2" resultMap="truckResult"/>
    <case value="3" resultMap="vanResult"/>
    <case value="4" resultMap="suvResult"/>
  </discriminator>
</resultMap>

//-----------------------------------------------------------------
// 或者 case 不指定结果集 ,直接指定映射关系
 <case value="1" resultType="carResult">
      <result property="doorCount" column="door_count" />
 </case>
//-----------------------------------------------------------------

使用   被指定的结果集   的映射关系
<resultMap id="carResult" type="Car">
  <result property="doorCount" column="door_count" />
</resultMap>

n+1 延迟加载

假设现在有 N 个关联关系完成了级联,那么只要再加入一个关联关系,就变成了 N+l
个级联,所有的级联 SQL 都会被执行 ,显然会有很多并不是我们关心的数据被取出,这样
会造成很大的资源浪费,这就是 N+l 问题,尤其是在那些需要高性能的互联网系统中,这
往往是不被允许的。

比 如作为 一个雇员的管理者,他只想看到员 工信息和员工任务信息,那么体检表和工牌的信
息就是多余的。

如果像上面那样取出所有属性,就会使数据库多执行几条毫无意义的 SQL 。
如果需要在雇员信息系统里加入一个关联信息,那么它在默认情况下会执行 SQL 取出数据,
而真实的需求往往只要完成雇员和雇员任务表的级联就可 以了,不需要把所有信息都加载
进来,因为有些信息并不常用,加载它们会多执行几条毫无用 处的 SQL,导致数据库资源
的损耗和系统性能的下降。

为了应对 N+l 问题, MyBatis 提供了延迟加载功能

即在一开始取雇员信息时,并不 需要将工牌表、体检表、任务表的记录取出 ,而是只将雇员信息和雇员任务表的信息取出 。 当我们通过雇员 POJO 访问工牌表时,体检表和任务表的记录时才通过对应的 SQL 取出,

image-20211210215138555

image-20211210215249465

选项 lazyLoadingEnabled 决定是否开启延迟加载 , 而选项 aggressiveLazyLoading 则控
制是否采用层级加载,但是它们都是全局性的配置,并不能解决我们的需求。加载雇员 信
息时,只加载雇员任务信息,因为层级加载会把工牌信息也加载进来 。 为了处理这个问题,
在 MyBatis 中使用 fetchType 属性,它可以处理全局定义无法处理的问题,进行自定义 。

fetch Type 出现在级联元素( association 、 collection ,注意, discriminator 没有这个属性可配置)中 ,它存在着两个值:

eager,获得当前 POJO 后立即加载对应的数据 。

lazy ,获得当前 POJO 后延迟加载对应的数据。

image-20211210215646215

缓存

一级续存和二级缓存

一级缓存是在 SqI Session 上的缓存, 默认开启

二级缓存是在 SqlSessionFactory 上的缓存

一级缓存

当一个 SqlSession 第一次通过 SQL 和参数获取对 象后,它就会将其缓存起来,如果下次的 SQL 和参数都没有发生变化, 并且缓存没有超时 或者声明需要刷新时 , 那么它就会从缓存中获取数据 ,而不是通过 SQL 获取了

注意 commit()方法的使用,如果不进行 commit , 是不会有一级缓存存在的

二级缓存

这说明了 一级缓存是在 SqlSession 层面的,对于不同的 SqlSession 对象是不能共享的。为了使 SqlSession 对象之间共享相同的缓存,有时候需要开启 二级缓存,开启二级缓存很简单,只要在映射文件( RoleMapper.xml ) 上加入代码:

<cache/>

//  Mapper.xml 

这个时候 MyBatis 会序列化和反序列化对应的 POJO ,也就要求 POJO 是一个可序列化 的对象,那么它就必须实现Serializable 接口。

对角色类( Role )对象进行缓存,那 么就需要它实现 Serializable 接口 :

从日志中可以看到,不同的 SqlSession 在获取同一条记录,都只是发送过一次 SQL 获
取数据因为这个时候 MyBatis 将其保存在 SqlSessionFactory 层面 ,可以 提供给各个 Sq!Session 使用,只是它需要 一 个序列化和反序列化的过程而己,因此它需要实现 Serializable 接口。

配置cache

image-20211210221845473

可以使用自定义的缓存,只是实现类需要实现 MyBatis 的接口 org.apache.ibatis.cache. Cache

在现实中 ,我们可 以使用 Redis, MongoDB 或者其他常用的缓存 , 假设存在一个 Redis 的缓存实现类 com.ssm . chapter5 .cache.RedisCache ,那么可以这样配置它 :

<cache type="com.cache.RedisCache">
        <property name="host" value="localhost" />
</cache>


这样配置后,  MyBatis 会启用缓存,同时调用 setHost(String host)方法,去设置配置的 内容。

对于一些不想使用缓存的语句

<select flushCache= "false " useCache= ” true ” />
<insert flushCache="true" />
<update flushCache="true "/>
<delete flushCache="true "/>
存储过程
  1. 设计一个pojo , 属性为 输出 和 输入 参数

    class pojoName{
        private roleName;
        int total;
        Date execDate;
    }
  1. 使用 mapper

    <select id="aa" parameterType="pojoName" statementType="CALLABLE">
            { 
                call funname1 (
                    #{roleName,mode=IN,jdbcType=VARCHAR},
                    #{total,ode=OUT,jdbcType=INTEGER},
                    .......
                )
        }
    </select>

    指定 statemetType 为 CALLABLE,说明它是在使用存储过程,如果不这样声明那么
    这段代码将会抛出异常。

​ 在调度存储过程中放入参数对应 的属性 ,井且在属性上通过 mode 设置了其输入或者输出参数 ,指定对应的 jdbcType ,这样 MyBatis 就会使用对应 的 typeHandler 去处理对应的类型转换。

游标 待补充

动态sql

因为某些查询需要许多条件,比如查询角色,可以根据角色名称或 者备注等信息查询 ,当不输入名 称时使用名称作条件就不合适了

而 MyBatis 提供对 SQL 语句动态的组装能力, 使用 XML 的几个简单的元素 ,便能完成功态 SQL 的功能。大量的判断都可以在 MyBatis 的映射 XML 里面配置,以达到许多需要大量代码才能实现的功能,大大减少了代码量 ,这体现了 MyBatis 的灵活、高度可配置性和可维护性 。

test 判断字符串

test 用于条件判断语旬,它在 MyBatis 中使用广泛。test 的作用相当于判断真假,在大部分场景中 ,它都是用以判断空和非空的

<if test=" type='Y'.toString()">
        //ok
</if>

对于字符型直接比较

对于字符串 用 tostring方法比较

image-20211210224437659

if

它常常与 test 属性联合使用

根据角色名称 roleName 去查找角色 , 但是角色名称是一个选填条件,不填写 时,就不要用它作为条件
查询。

<select id="" ......    >
        select * from table1 where 1=1 
    <if test="roleName !=null and roleName !='' ">
        and role_name like ....
    </if>

</select>
choose 、when 、 otherwise 元素

switch... case ... default. . .功能的语句 。在映射器的动态语句中 choose 、 when 、 otherwise 这 3 个元素承担了这个功能

<select id="" ......    >
        select * from table1 where 1=1 
    
    
    <choose> // switch
    
         
        <when  test="roleName !=null and roleName !='' "> // case1
        and role_no= #{roleNo}  
        </when>
     
        <when  test=" ,,,,, ?条件2 ">   // case2
        and role_name like ....
         </when>
        
        <otherwise>  //default
                and ......
        </otherwise>
        
    
    </choose>
    
 

</select>
trim where set

上面示例 都加入了 一个条件 “ 1=1 ”,如果没有加入这个条件,那么可能就变为了这样一条错误的语句:

而加入了条件“ 1=1 ” 又显得相当奇怪,我们可以用 where 元素去处理 SQL 以达到预期效果,

<select id="" ......    >
        select * from table1  
    <where>
        <if test="roleName !=null and roleName !='' ">
            and role_name like ....
        </if>
    </where>
</select>

有时候要去掉的是一些特殊的 SQL 语法,比如常见的 and 、 or。而使用 trim 元素也可以达到预期效果

image-20211210225602222

上面的效果是 将 and 替换为 where

update 中 set 元素

image-20211210225845183

foreach

image-20211210230030943

bind

bind 元素的作用是通过 OGNL 表达式去自定义一个上下文变量,这样更方便使用。

在进行模糊查询时,如果是 MySQL 数据库,常常用到的是一个 concat,它用“%”和参数相
连。

然而在 Oracle 数据库则没有,Oracle 数据库用连接符号“||”,这样 SQL 就需要提供两
种形式去实现。但是有了 bind 元素,就不必使用数据库的语言 ,而是使用 MyBatis 的动态
SQL 即可完成。

image-20211210230538485

传递进来的参数,它和通配符(%)连接后赋给了 pattern,然后就可以在 se lect 语句中使用这个变量进行模糊查询了

运行原理

My Batis 的运行过程分为两大步:

第1 步,读取配置文件缓存到 Configuration 对象,用以创建 Sq!SessionFactory :

第 2 步,SqI Session 的执行过程 。

Sq!Ses sion 的执行过程就不是那么简单了,它包括许多复杂 的技术,要先掌握反射技术和动态代理技术,这是揭示 MyBatis 底层构架的基础

SqlSessionFactory

它采用了 Builder 模式去创建 SqlSessionFactory , 在实际中可以通过 SqlSessionFactoryBuilder 去构建 ,
其构建分为两步。

  1. 通过 org.apache.ibatis .builder且nl .XMLConfigBuilder 解析配置的 XML 文件 ,读出所配置的参数,并将读取的内容存入 Configuration 类对象中。 而 Configuration 采用的是单例模式 ,几乎所有 的 MyBatis 配置内容都会存放在这个单例对象中,以便后续将这些内容读出。
  2. 使用 Confinguration 对象去创建 SqlSessionFactory 。 默认的实现类 DefaultSqlSessionFactory

生成sql

private String selectPersonSql() {
  return new SQL() {{
    SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
    SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
    FROM("PERSON P");
    FROM("ACCOUNT A");
    INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
    INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
    WHERE("P.ID = A.ID");
    WHERE("P.FIRST_NAME like ?");
    OR();
    WHERE("P.LAST_NAME like ?");
    GROUP_BY("P.ID");
    HAVING("P.LAST_NAME like ?");
    OR();
    HAVING("P.FIRST_NAME like ?");
    ORDER_BY("P.ID");
    ORDER_BY("P.FULL_NAME");
  }}.toString();
}