手把手教你Mybatis(1)

1,183 阅读22分钟

简介及下载

简介

MyBatis是支持定制化SQL、存储过程以及高级映射的持久层框架(半自动化的)。

MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。

下载

下载地址:github.com/mybatis/myb…

官方文档: https://mybatis.org/mybatis-3/

解压后的此jar包就是开发时需要用到的:

如果使用Maven的话,在pom.xml文件中添加:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

初始案例

1、库表准备

create table tbl_employee(
  id int(11) primary key auto_increment,
  last_name varchar(255),
  gender char(1),
  email varchar(255)
)

insert into tbl_employee values(null, "tom", 0, "tom@163.com");
insert into tbl_employee values(null, "Mikel", 1, "Mikel@qq.com");

select * from tbl_employee;

2、工程目录

如果是IDEA创建的普通Java工程,目录结构如下:

3、JavaBean对象Employee

public class Employee {
    private Integer id;
    private String lastName; // 注意这里在SQL中的字段是last_name,不一样
    private String email;
    private String gender;

    ...
}

4、DAO接口

package com.code.mybatis.dao;

import com.code.mybatis.bean.Employee;

public interface EmployeeMapper {
    public Employee getEmpById(Integer id);
}

5、全局配置文件mybatis-config.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>
  <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://localhost:3306/mybatis_demo" />
        <property name="username" value="root" />
        <property name="password" value="root1234" />
      </dataSource>
    </environment>
  </environments>
  <!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
  <mappers>
    <mapper resource="EmployeeMapper.xml" />
  </mappers>
</configuration>

6、SQL映射文件EmployeeMapper.xml

相当于是定义Dao接口的实现类如何工作。这也是我们使用MyBatis时编写的最多的文件

<?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="com.code.mybatis.dao.EmployeeMapper">
    <!--
    namespace:名称空间,指定为接口的全类名。相当于这个配置文件绑定了EmployeeMapper接口
    id:标签的唯一标识,和方法名一致
    resultType:返回值类型
    #{id}:从传递过来的参数中取出id值
    public Employee getEmpById(Integer id);
     -->
    <select id="getEmpById" resultType="com.code.mybatis.bean.Employee">
		select id,last_name lastname,gender,email from tbl_employee where id = #{id}
    </select>
</mapper>

7、测试类

  • SqlSessionFactory的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。(抽离出来前三行)
  • SqlSession的实例不是线程安全的,因此是不能被共享的(不能定义为全局变量,要使用一次创建一次)。它的最佳的作用域是请求或方法作用域。
  • SqlSession每次使用完成后需要正确关闭,这个关闭操作是必须的。
  • SqlSession可以直接调用方法的id进行数据库操作,但是我们一般还是推荐使用SqlSession获取到Dao接口的代理类,执行代理对象的方法。 可以更安全的进行类型检查操作。
package com.code.mybatis.test;

import com.code.mybatis.bean.Employee;
import com.code.mybatis.dao.EmployeeMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

public class MybatisTest {

    // 老方法-不建议
    @Test
    public void test() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // SqlSession可直接执行已经映射的SQL语句
        SqlSession openSession = sqlSessionFactory.openSession();
        Employee employee = openSession.selectOne("com.code.mybatis.dao.EmployeeMapper.getEmpById", 1);
        System.out.println(employee);

        openSession.close();
    }

    // 新方式:接口式编程(推荐)
    @Test
    public void testSelect() throws IOException {
        // 1、获取SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 2、获取SqlSession对象(一个SqlSession对象代表和数据库的一次会话。 )
        // 		SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
        SqlSession openSession = sqlSessionFactory.openSession();
        // 3、获取接口的实现类-映射器
        EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp = employeeMapper.getEmpById(2);
        System.out.println(emp);

        openSession.close();
    }
}

全局配置文件

配置文件结构

MyBatis 的配置文件包含了设置和属性信息。 配置文档的顶层结构如下:

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

properties属性

作用:引入外部配置文件(基本用不到,后面Spring来管理)

  • resource:引入类路径下的资源文件
  • url:引入网络或者磁盘路径下的资源文件

例如:数据库配置文件可以写为单独的dbconfig.properties,然后在mybatis-config.xml中通过properties引用进来

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_demo
jdbc.username=root
jdbc.password=root1234
    <properties resource="dbconfig.properties"></properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
    </environments>

如果属性在不只一个地方进行了配置,那么MyBatis将按照下面的顺序来加载:

  1. 在properties元素体内指定的属性首先被读取。
  2. 然后根据properties元素中的resource属性读取类路径下属性文件或根据url属性指定的路径读取属性文件,并覆盖已读取的同名属性。
  3. 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。(通过方法参数传递的属性具有最高优先级)

settings设置

常用参数如下:

  • cacheEnabled:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。可选 true | false 。默认值true
  • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。可选true | false。默认值false
  • useColumnLabel:使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。可选true | false。默认值true
  • mapUnderscoreToCamelCase:是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。可选true | false。默认值False
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/> <!--驼峰命名规则映射-->
    <setting name="lazyLoadingEnabled" value="true"/> <!--开启懒加载-->
    <setting name="aggressiveLazyLoading" value="false"/> <!--侵入延迟加载-->
    <setting name="cacheEnabled" value="true"/> <!--开启二级缓存-->
</settings>

这里也无需添加别名:

可以没添加配置时:

添加配置后:

typeAliases别名处理器

类型别名是为Java类型设置一个短的名字,可以方便我们引用某个类。

  • type:要起别名类型的全路径类名。
  • alias:别名。如果没写,那么会有一个默认的别名,就是简单类名小写
  • package:类很多的情况下,可以批量设置别名这个包下的每一个类创建一个默认的别名,就是简单类名小写。 若有注解,则别名为其注解值。
<typeAliases>
  <!--<typeAlias type="com.code.mybatis.bean.Employee" alias="employee" />-->
  <package name="com.code.mybatis.bean"/>
</typeAliases>

使用的时候就可以:

<select id="getEmp" resultType="employee" databaseId="mysql">
  select * from tbl_employee where id = #{id}
</select>

在批量起别名的情况下,也可以使用@Alias注解为其指定一个别名。使用的时候就是取的名字

@Alias("emp")
public class Employee {}

MyBatis已经为许多常见的Java类型内建了相应的类型别名。它们都是大小写不敏感的,我们在起别名的时候千万不要占用已有的别名。

typeHandlers类型处理器

无论是MyBatis在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成Java类型,常见类型如下:

对于日期的处理,MyBatis3.4以前的版本需要手动注册这些处理器,以后的版本都是自动注册的:

自定义类型处理器:我们可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型

plugins插件

插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)
<!--插件配置-->
<plugins>
    <plugin interceptor="com.code.mybatis.plugin.MyFirstPlugin">
        <property name="username" value="tomcat"/>
        <property name="password" value="123456"/>
    </plugin>
    <plugin interceptor="com.code.mybatis.plugin.MySecondPlugin">
    </plugin>
</plugins>

environments环境

  • MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置。
  • 每种环境使用一个environment标签进行配置并指定唯一标识符(id)
  • 可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境
  • 每个数据库对应一个SqlSessionFactory实例

environment

    <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://localhost:3306/mybatis_demo" />
                <property name="username" value="root" />
                <property name="password" value="root1234" />
            </dataSource>
        </environment>
    </environments>

id:指定当前环境的唯一标识

transactionManager和dataSource都必须有

transactionManager

type:JDBC | MANAGED | 自定义

  • JDBC:使用了JDBC的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围。实际使用的是JdbcTransactionFactory
  • MANAGED:不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如JEE 应用服务器的上下文)。实际使用的是ManagedTransactionFactory
  • 自定义:实现TransactionFactory接口,type=全类名/别名

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

dataSource

type:UNPOOLED | POOLED | JNDI | 自定义

  • UNPOOLED:不使用连接池,UnpooledDataSourceFactory

  • POOLED:使用连接池,PooledDataSourceFactory

  • JNDI:在EJB或应用服务器这类容器中查找指定的数据源

  • 自定义:实现DataSourceFactory接口,定义数据源的获取方式。

实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置

databaseIdProvider数据库厂商标识

MyBatis可以根据不同的数据库厂商执行不同的语句:

    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql"/>
        <property name="Oracle" value="oracle"/>
        <property name="SQL Server" value="sqlserver"/>
        <property name="DB2" value="db2"/>
    </databaseIdProvider>

Type:DB_VENDOR

  • 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider接口来自定义。
  • DB_VENDOR会通过DatabaseMetaData#getDatabaseProductName()返回的字符串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短。

Property-name:数据库厂商标识

Property-value:为标识起一个别名,方便SQL语句使用databaseId属性引用

    <select id="getEmpById" resultType="com.code.mybatis.bean.Employee" databaseId="mysql">
		select id,last_name lastname,gender,email from tbl_employee where id = #{id}
	</select>

MyBatis匹配规则如下:

  1. 如果没有配置databaseIdProvider标签,那么databaseId=null
  2. 如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为null
  3. 如果databaseId不为null,他只会找到配置databaseId的sql语句 MyBatis会加载不带databaseId属性和带有匹配当前数据库databaseId属性的所有语句。
  4. 如果同时找到带有databaseId和不带databaseId的相同语句,则后者会被舍弃。

mappers映射

mapper逐个注册SQL映射文件:

  • resource:类路径下的sql映射文件
  • url:网络上或者磁盘上的sql映射文件
  • class:注册接口的方式(接口的全类名) (或者方法上添加注解,不提供映射文件)

    <mappers>
        <mapper resource="EmployeeMapper.xml"/>
        <mapper class="com.code.mybatis.dao.EmployeeMapperAnnotion" />
    </mappers>
public interface EmployeeMapperAnnotion {
    @Select("select * from tbl_employee where id = #{id}")
    public Employee getEmpById(Integer id);
}

或者使用批量注册(这种方式要求SQL映射文件名必须和接口名相同并且在同一目录下

    <mappers>
        <package name="com.code.mybatis.dao"/>
    </mappers>

最佳实践:

  • 比较重要或复杂的接口,需要提供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>
    <!--引入外部配置文件,这里读取jdbc的配置-->
    <properties resource="dbconfig.properties"></properties>
    <!--mybatis的调整设置-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/> <!--驼峰命名规则映射-->
    </settings>
    <!--为Java类型设置一个别名-->
    <typeAliases>
        <!--<typeAlias type="com.code.mybatis.bean.Employee" alias="employee" />-->
        <package name="com.code.mybatis.bean"/>
    </typeAliases>
    <!--不同环境配置-->
    <environments default="stable">
        <!--开发环境-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <!--正式环境-->
        <environment id="stable">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--数据库环境,支持多数据库-->
    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql"/>
        <property name="Oracle" value="oracle"/>
        <property name="SQL Server" value="sqlserver"/>
    </databaseIdProvider>
    <!-- sql映射文件要注册到全局配置文件中 -->
    <mappers>
        <!--<mapper resource="EmployeeMapper.xml"/>
        <mapper class="com.code.mybatis.dao.EmployeeMapperAnnotion" />-->
        <package name="com.code.mybatis.dao"/>
    </mappers>
</configuration>

映射文件

映射文件指导着MyBatis如何进行数据库增删改查。 SQL映射文件的顶级元素如下(按照应被定义的顺序列出):

  • cache :该命名空间的缓存配置
  • cache-ref :引用其它命名空间的缓存配置
  • resultMap(重要):描述如何从数据库结果集中加载对象
  • parameterMap : 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。
  • sql(重要):可被其它语句引用的可重用语句块
  • insert(重要):映射插入语句
  • update(重要):映射更新语句
  • delete(重要):映射删除语句
  • select(重要):映射查询语句

insert & update & delete

  • id(重要):在命名空间中唯一的标识符,可以被用来引用这条语句
  • parameterType(重要) :将要传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为MyBatis可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)
  • flushCache:将其设置为true后,只要语句被调用都会导致本地缓存和二级缓存被清空,默认值:true(对应insert、update和delete 语句)
  • 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 (重要) :如果配置了数据库厂商标识(mybatis-config配置文件里的databaseIdProvider),MyBatis会加载所有不带databaseId或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略
<?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="com.code.mybatis.dao.EmployeeMapper">
    <!--查询:public Employee getEmpById(Integer id);-->
    <select id="getEmpById" resultType="employee" databaseId="mysql">
		select id,last_name,gender,email from tbl_employee where id=#{id}
		</select>

    <!--增加:public Integer addEmp(Employee employee);-->
    <insert id="addEmp" parameterType="employee">
        insert into tbl_employee(last_name, gender, email) values(#{lastName}, #{gender}, #{email})
    </insert>

    <!--修改:public void updateEmp(Employee employee);-->
    <update id="updateEmp" parameterType="employee">
        update tbl_employee set last_name=#{lastName}, gender=#{gender}, email=#{email} where id=#{id}
    </update>

    <!--删除:public void deleteEmpById(Integer id);-->
    <delete id="deleteEmpById">
        delete from tbl_employee where id=#{id}
    </delete>

</mapper>
@Test
public void testAdd() throws IOException {
	SqlSession openSession = getSqlSession();
	EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
	Employee employee = new Employee(null, "TestAdd", "TestAdd@qqqq.com", "1");
	Integer result = employeeMapper.addEmp(employee);
	openSession.commit(); // 需要手动提交,除非获取SqlSession的时候openSession()传入true  
	System.out.println(result);
	openSession.close();
}

主键生成方式

作用:插入或者修改数据后,取出由数据库内部生成的主键

若数据库支持自动生成主键的字段(比如MySQL 和 SQL Server),则可以设置useGeneratedKeys=”true” ,然后再把keyProperty设置到目标属性上

<insert id="addEmp" parameterType="employee" useGeneratedKeys="true" keyProperty="id">
  insert into tbl_employee(last_name, gender, email) values(#{lastName}, #{gender}, #{email})
</insert>
@Test
public void testAdd2() throws IOException {
	SqlSession openSession = getSqlSession();
	EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
	Employee employee = new Employee(null, "primaryKey", "primaryKey@qqqq.com", "1");
	employeeMapper.addEmp(employee);
	System.out.println(employee.getId()); // 可以直接获取,结果为7
	openSession.close();
}

而对于不支持自增型主键的数据库(例如 Oracle 使用序列),则可以使用selectKey子元素:selectKey元素将会首先运行,id会被设置,然后插入语句会被调用

    <insert id="addEmp" parameterType="employee" databaseId="oracle">
        <selectKey order="BEFORE" keyProperty="id" resultType="_int">
            SELECT emp_seq.nextval FROM dual
        </selectKey>
        insert into tbl_employee(last_name, gender, email) values(#{lastName}, #{gender}, #{email})
    </insert>

[selectKey]

  • keyProperty:selectKey语句结果 (查出的主键)应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称
  • keyColumn:返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称
  • resultType:结果的类型
  • order:可以设置为 BEFORE 或AFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用
  • statementType:和前面一样,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 类型的映射语句,分别代表 Statement, PreparedStatement 和 CallableStatement 类型

参数传递

#{参数名}

  • 单个参数
    • 可以接受基本类型、对象类型、集合类型的值。这种情况MyBatis可直接使用这个参数,不需要经过任何处理。
  • 多个参数
    • 任意多个参数,都会被MyBatis重新包装成一个Map传入Map的key是param1,param2,0,1...,值就是参数的值
  • 命名参数
    • 参数使用@Param起一个名字,MyBatis就会将这些参数封装进map中,key就是我们自己指定的名字
  • POJO
    • 当这些参数属于我们业务POJO时,我们直接传递POJO
  • Map
    • 我们也可以封装多个参数为map,直接传递 。不经常使用的情况下使用
  • TO(Transfer Object)
    • 如果多个参数不是业务模型中的数据,但是要经常使用,推荐使用TO数据传输对象
    • 如:Page{ int index, int size }

最佳实践:参数多时会封装map,为了不混乱,可以使用@Param来制定封装时使用的key。#{key} 就可以取出map中的值

单个参数:

<!--查询:public Employee getEmpById(Integer id);-->
<select id="getEmpById" resultType="employee" databaseId="mysql">
	select id,last_name,gender,email from tbl_employee where id=#{id}
</select>

多个参数+参数命名:

// 查询方法
public Employee getEmpByIdAndLastName(
    @Param("id") Integer id, @Param("lastName") String lastName);
<!--查询:public Employee getEmpByIdAndLastName(Integer id, String lastName);-->
<select id="getEmpByIdAndLastName" resultType="employee" databaseId="mysql">
	select id,last_name,gender,email from tbl_employee 
  	where id=#{id} AND last_name=#{lastName }
</select>

传入POJO:

<update id="updateEmp" parameterType="employee">
	update tbl_employee set last_name=#{lastName}, gender=#{gender}, email=#{email} where id=#{id}
</update>

传入Map:

// 查询方法
public Employee getEmpByMap(Map<String, Object> map);
<!--查询:public Employee getEmpByMap(Map<String, Object> map);-->
<select id="getEmpByMap" resultType="employee" databaseId="mysql">
	select id,last_name,gender,email from tbl_employee where id=#{id} AND last_name=#{lastName}
</select>
    @Test
    public void testSelect3() throws IOException {
        SqlSession openSession = getSqlSession();
        EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("id", 2);
        map.put("lastName", "Mikel");
        Employee emp = employeeMapper.getEmpByMap(map);
        System.out.println(emp);
        openSession.close();
    }

场景思考

public Employee getEmpById(@Param("id")Integer id, String lastName);
取值:id-->#{id/param1}   lastName-->#{param2}

public Employee getEmpById(Integer id, @Param("e")Employee emp);
取值:id-->#{param1}   lastName-->#{param2.lastName/e.lastName}

Collection或者数组都会封装在map中
	Collection-collection
	List-list
    数组-array
public Employee getEmpById(List<Integer> ids);
取值:取第一个id的值:#{list[0]}

#与$取值的区别:

  • #{key}:获取参数的值,预编译到SQL中。安全的。 (相当于PreparedStaterment)。大部分情况下使用
  • ${key}:获取参数的值,拼接到SQL中。有SQL注入问 题。某些情况下使用,如表名、分表、排序等
    • select * from ${year}_salary where XXX;
    • select * from tableName order by 字段名{字段名} {desc/aesc}
    <update id="lowerShelf" parameterType="com.goblin.dal.model.DeviceInfoDO">
        update <include refid="table_name"/>
        set status=4 where id=${deviceId};
    </update>

参数处理

参数位置支持的属性:

javaType、jdbcType、mode(存储过程)、numericScale(小数位数)、resultMap、typeHandler、jdbcTypeName、expression

参数也可以指定一个特殊的数据类型。

  • javaType通常可以从参数对象中来去确定
  • 如果null被当作值来传递,对于所有可能为空的列,jdbcType需要被设置
  • 对于数值类型,还可以设置小数点后保留的位数:
  • mode属性允许指定 IN,OUT 或 INOUT 参数。如果参数为OUT或INOUT,参数对象属性的真实值将会被改变, 就像在获取输出参数时所期望的那样。

实际上通常被设置的是:可能为空的列名指定 jdbcType

<select id="searchByTime" resultMap="BaseResultMapRes">
	select * from <include refid="table_name"/>
	where createTime between #{startTime,jdbcType=TIMESTAMP} 
  and #{endTime,jdbcType=TIMESTAMP}
</select>

select查询

基本例子:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

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

会生成预处理语句:
SELECT * FROM PERSON WHERE ID=?

【select的属性】

  • id(重要) :在命名空间中唯一标识符,可以被用来引用这条语句。需要和接口的方法名一致
  • parameterType(重要) :将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)
  • resultType(重要) :期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型而不是集合本身的类型resultType 和 resultMap 之间只能同时使用一个
  • resultMap(重要)对外部 resultMap 的命名引用。 resultType 和 resultMap 之间只能同时使用一个。
  • flushCache:将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false
  • useCache:将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true
  • timeout:这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)
  • fetchSize:这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)
  • statementType:可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED
  • resultSetType:FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)
  • databaseId:如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略
  • resultOrdered:这个设置仅针对嵌套结果 select 语句:如果为 true,则假设结果集以正确顺序(排序后)执行映射,当返回新的主结果行时,将不再发生对以前结果行的引用。 这样可以减少内存消耗。默认值:false
  • resultSets:这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔
	<!--查询:public Employee getEmpById(Integer id);-->
    <select id="getEmpById" resultType="employee" databaseId="mysql">
		select id,last_name,gender,email from tbl_employee where id=#{id}
  </select>

	<!--查询:public List<Employee> getEmpByLastNameLike(String lastName);-->
    <select id="getEmpByLastNameLike" resultType="employee" databaseId="mysql">
		select * from tbl_employee where last_name like #{lastName}
	</select>

  <!--查询:public Employee getEmpByIdAndLastName(Integer id, String lastName);-->
    <select id="getEmpByIdAndLastName" resultType="employee" databaseId="mysql">
		select id,last_name,gender,email from tbl_employee where id=#{id} AND last_name=#{lastName }
	</select>

  <!--查询:public Employee getEmpByMap(Map<String, Object> map);-->
    <select id="getEmpByMap" resultType="employee" databaseId="mysql">
		select id,last_name,gender,email from tbl_employee where id=#{id} AND last_name=#{lastName}
	</select>

select记录封装map

单条记录:key是列名,值是对应的值

多条记录:key是定义的值,值是封装的bean对象。需要使用 @MapKey("id") 来指定是哪个值

<!--查询方法,key是列名,值是对应的值
	public Map<String, Object> getEmpByIdReturnMaps(Integer id);-->
<!--运行结果:
	{gender=1, last_name=Mikel, id=2, email=Mikel@qq.com}-->
<select id="getEmpByIdReturnMaps" resultType="map" databaseId="mysql">
	select * from tbl_employee where id=#{id}
</select>


<!--查询方法,多条记录:key是记录的主键,值是封装的bean对象
	@MapKey("id")
	public Map<Integer, Employee> getEmpByLastNameLikeReturnMaps(String lastName);-->
<!--运行结果:
	{1=Employee [id=1, lastName=tom, email=tom@163.com, gender=0],
	5=Employee [id=5, lastName=TestAdd, email=TestAdd@qqqq.com, gender=1],
	6=Employee [id=6, lastName=TestAdd, email=TestAdd@qqqq.com, gender=1]}-->
<select id="getEmpByLastNameLikeReturnMaps" resultType="employee" databaseId="mysql">
	select * from tbl_employee where last_name like #{lastName}
</select>

自动映射

两种方法:

  1. 全局setting设置
    1. autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean属性名一致
    2. 如果autoMappingBehavior设置为null则会取消自动映射
    3. 数据库字段命名规范,POJO属性符合驼峰命名法,如 A_COLUMN-->aColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true
  2. 自定义resultMap,实现高级结果集映射(大型项目常用)

结果映射resultMap

ResultMap的【属性列表】:

  • id:当前命名空间中的一个唯一标识,用于标识一个结果映射
  • type:类的完全限定名, 或者一个类型别名。(自定义规则的Java类型)
  • autoMapping:如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。

resultMap的【子元素】:

  • constructor:用于在实例化类时,注入结果到构造方法中
    • idArg:ID 参数,标记出作为 ID 的结果可以帮助提高整体性能
    • arg :将被注入到构造方法的一个普通结果
  • id:一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 (指定主键列的封装规则)
  • result:注入到字段或 JavaBean 属性的普通结果 (指定普通列的封装规则)
  • association :一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射:关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection:一个复杂类型的集合
    • 嵌套结果映射:集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator:使用结果值来决定使用哪个 resultMap
    • case:基于某些值的结果映射
      • 嵌套结果映射: case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射

id & result 子元素

id和result映射一个单独列的值到简单数据类型 (字符串,整型,双精度浮点数,日期等)的属性或字段

  • property映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
  • column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
  • javaType:一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
  • jdbcType支持的 JDBC 类型。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。
  • typeHandler:类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。
<?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="com.code.mybatis.dao.EmployeeMapperPlus">

    <resultMap id="EmployeeResultMap" type="com.code.mybatis.bean.Employee">
        <id column="id" property="id" />
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/> <!--不指定也可以,会自动封装-->
        <result column="email" property="email"/>
    </resultMap>

    <!--查询:public Employee getEmpById(Integer id);-->
    <!--<select id="getEmpById" resultType="employee" databaseId="mysql">
        select * from tbl_employee where id=#{id}
    </select>-->
    <select id="getEmpById" resultMap="EmployeeResultMap" databaseId="mysql">
        select * from tbl_employee where id=#{id}
    </select>

</mapper>

关联查询(级联属性封装)

    <resultMap id="EmployeeAndDeptResultMap" type="com.code.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <result column="did" property="dept.id" /> <!--属性的属性-->
        <result column="dept_name" property="dept.deptName" /> <!--属性的属性-->
    </resultMap>


    <!--查出员工信息的同时,也查出部门信息-->
    <!--查询:public Employee getEmpAndDept(Integer id);-->
    <select id="getEmpAndDept" resultMap="EmployeeAndDeptResultMap">
        select e.id id,e.last_name last_name,e.gender gender,e.email email,d.id did,d.dept_name dept_name
        from tbl_employee e,tbl_dept d where e.d_id=d.id and e.id=#{id}
    </select>

association(嵌套结果集)

复杂对象映射

POJO中的属性可能会是一个对象。我们可以使用联合查询,并以级联属性的方式封装对象

使用association标签定义对象的封装规则:

    <resultMap id="EmployeeAndDeptResultMap2" type="com.code.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <!--对象里的属性,定义为联合的对象-->
        <association property="dept" javaType="com.code.mybatis.bean.Department">
            <id column="did" property="id"/>
            <result column="dept_name" property="deptName"/>
        </association>
    </resultMap>

association分步查询

  • select:调用目标的方法查询当前属性的值
  • column:将指定列的值传入目标方法
    <resultMap id="EmployeeAndDeptResultMap3" type="com.code.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <!--对象里的属性,定义为联合的对象。-->
        <association property="dept"
                     select="com.code.mybatis.dao.DepartmentMapper.getDeptById" column="d_id">
        </association>
    </resultMap>

    <!--public Employee getEmpByIdStep(Integer id);-->
    <select id="getEmpByIdStep" resultMap="EmployeeAndDeptResultMap3">
        select * from tbl_employee where id=#{id}
    </select>

其中DepartmentMapper如下:

<mapper namespace="com.code.mybatis.dao.DepartmentMapper">
    <!--查询方法   public Department getDeptById(Integer id);-->
    <select id="getDeptById" resultType="department">
        select * from tbl_dept where id=#{id}
    </select>
</mapper>

association延迟加载

分步查询可以使用延迟加载。(延迟加载:关联的值当使用的时候再去查询,而不是每次都查询)

在分布查询的基础之上,配置开启延迟加载和属性按需加载(在mybatis-config.xml中):

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/> <!--驼峰命名规则映射-->
        <setting name="lazyLoadingEnabled" value="true"/> <!--开启懒加载-->
        <setting name="aggressiveLazyLoading" value="false"/> <!--侵入延迟加载-->
    </settings>

collection集合类型嵌套结果集

collection:定义关联集合类型的属性的封装规则

ofType:指定集合中元素的类型

    <!--查询部门信息及其所有员工信息:
        public Department getDeptAndAllEmpById(Integer id);-->
    <resultMap id="DeptAndAllEmpResultMap" type="com.code.mybatis.bean.Department">
        <id column="did" property="id"/>
        <result column="dept_name" property="deptName"/>
        <collection property="emps" ofType="com.code.mybatis.bean.Employee">
            <id column="eid" property="id" />
            <result column="last_name" property="lastName"/>
            <result column="gender" property="gender"/>
            <result column="email" property="email"/>
        </collection>
    </resultMap>
    <select id="getDeptAndAllEmpById" resultMap="DeptAndAllEmpResultMap">
        select d.id did,d.dept_name dept_name,
            e.id eid,e.last_name last_name,e.email email,e.gender gender
        from tbl_dept d left join tbl_employee e
        on d.id=e.d_id where d.id=#{id}
    </select>

collection 分步查询

    <!--public Department getDeptByIdStep(Integer id);-->
    <resultMap id="DeptAndAllEmpResultMap2" type="com.code.mybatis.bean.Department">
        <id column="id" property="id"/>
        <result column="dept_name" property="deptName"/>
        <collection property="emps" select="com.code.mybatis.dao.EmployeeMapperPlus.getEmpByDeptId"
                    column="id"></collection>
    </resultMap>
    <select id="getDeptByIdStep" resultMap="DeptAndAllEmpResultMap2">
        select * from tbl_dept where id=#{id}
    </select>

多列值封装map传递

分步查询的时候通过column指定,将对应的列的数据传递过去,我们有时需要传递多列数据。 使用{key1=column1,key2=column2...}的形式

    <!--public Department getDeptByIdStep(Integer id);-->
    <resultMap id="DeptAndAllEmpResultMap3" type="com.code.mybatis.bean.Department">
        <id column="id" property="id"/>
        <result column="dept_name" property="deptName"/>
        <collection property="emps" select="com.code.mybatis.dao.EmployeeMapperPlus.getEmpByDeptId"
                    column="{id=id}"></collection>
    </resultMap>
    <select id="getDeptByIdStep" resultMap="DeptAndAllEmpResultMap3">
        select * from tbl_dept where id=#{id}
    </select>

association或者collection标签的 fetchType=eager/lazy 可以覆盖全局的延迟加载策略, 指定立即加载(eager)或者延迟加载(lazy)

discriminator鉴别器

鉴别器(了解即可):mybatis可以使用discriminator判断某列的值,然后根据其值改变封装行为

  • column:指定判断的列名
  • javaType:列值对应的Java类型
    <resultMap id="EmployeeAndDeptResultMap4" type="com.code.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <discriminator javaType="string" column="gender">
            <!--代表是女生,查询出来部门信息-->
            <case value="0" resultType="com.code.mybatis.bean.Employee">
                <association property="dept"
                             select="com.code.mybatis.dao.DepartmentMapper.getDeptById" column="d_id">
                </association>
            </case>
            <!--代表是男生,把lastName的结果赋值给email-->
            <case value="1" resultType="com.code.mybatis.bean.Employee">
                <id column="id" property="id"/>
                <result column="last_name" property="lastName"/>
                <result column="gender" property="gender"/>
                <result column="last_name" property="email"/>
            </case>
        </discriminator>
    </resultMap>

Java API

目录结构

目录结构最佳实践,典型的应用目录结构如下:

/my_application
  /bin
  /devlib
  /lib <-- MyBatis *.jar 文件在这里。
  /src
    /org/myapp/
      /action
      /data <-- MyBatis 配置文件在这里,包括映射器类、XML配置、XML映射文件。
        /mybatis-config.xml
        /BlogMapper.java
        /BlogMapper.xml
      /model
      /service
      /view
    /properties <-- 在 XML 配置中出现的属性值在这里。
  /test
    /org/myapp/
      /action
      /data
      /model
      /service
      /view
    /properties
  /web
    /WEB-INF
      /web.xml

SqlSession

SqlSessions 是由 SqlSessionFactory 实例创建的。SqlSessionFactory 对象包含创建 SqlSession 实例的各种方法。而 SqlSessionFactory 本身是由 SqlSessionFactoryBuilder 创建的,它可以从 XML、注解或 Java 配置代码来创建 SqlSessionFactory。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession openSession = sqlSessionFactory.openSession();
EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);

当 Mybatis 与一些依赖注入框架(如Spring)搭配使用时,SqlSession 将被依赖注入框架创建并注入,所以不需要使用 SqlSessionFactoryBuilder 或者 SqlSessionFactory。

SqlSessionFactoryBuilder

最常用的build()方法:SqlSessionFactory build(InputStream inputStream)

可选的参数是 environment(加载哪种环境) 和 properties(加载配置属性)

SqlSessionFactory

已重载的多个 openSession() 方法:

SqlSession openSession()
SqlSession openSession(boolean autoCommit)  // 常用!!!!!
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();

SqlSession

它包含了所有执行语句、提交或回滚事务以及获取映射器实例的方法。

  • 语句执行方法

    • 执行定义在SQL映射XML文件中的SELECT、INSERT、UPDATE和DELETE语句。入参是语句的ID以及参数对象
    • T selectOne(String statement, Object parameter)
    • List selectList(String statement, Object parameter)
    • Cursor selectCursor(String statement, Object parameter)
    • <K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
    • int insert(String statement, Object parameter)
    • int update(String statement, Object parameter)
    • int delete(String statement, Object parameter)
  • 立即批量更新方法

    • 将 ExecutorType 设置为 ExecutorType.BATCH 时,可以使用这个方法清除(执行)缓存在 JDBC 驱动类中的批量更新语句
    • List flushStatements()
  • 事务控制方法

    • 默认情况下 MyBatis 不会自动提交事务,除非它侦测到调用了插入、更新或删除方法改变了数据库
    • void commit()
    • void commit(boolean force)
    • void rollback()
    • void rollback(boolean force)
  • 本地缓存

    • void clearCache()-->随时调用以下方法来清空本地缓存
  • 确保 SqlSession 被关闭

    • void close ()
  • 使用映射器

    • T getMapper(Class type)

学习资料:官方文档: https://mybatis.org/mybatis-3/