前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis学习笔记

Mybatis学习笔记

作者头像
玛卡bug卡
发布2022-09-20 11:03:50
8070
发布2022-09-20 11:03:50
举报
文章被收录于专栏:Java后端修炼

1、概述

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

Mybatis与其他的ORM框架不同,不是将对象与数据库表联系起来,而是将Java方法与SQL语句相关联。相比 Hibernate (全自动ORM框架),Mybatis属于是半自动ORM,在Mybatis上进行SQL优化/SQL语句修改等操作会比较简单,在Hibernate上还需要转换为HQL语言。

2、简单配置

1)环境准备

使用maven进行依赖管理的项目中,只需要引入mybatis的坐标(以及mysql相关),这里以mybatis 3.5.7为例:

代码语言:javascript
复制
<dependency>
    <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.7</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

为了显示清晰还可以引入log4j日志依赖,之后在类路径下添加配置文件:

代码语言:javascript
复制
log4j.rootLogger=error, stdout
log4j.logger.top.jtszt=TRACE
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

之后准备一个数据库表用于CRUD操作(同时写入几条测试数据):

代码语言:javascript
复制
CREATE DATABASE mybatis_db2;
USE mybatis_db2;
CREATE TABLE t_employee2(
    id INT PRIMARY KEY AUTO_INCREMENT,
    ename VARCHAR(50),
    gender INT,
    email VARCHAR(50),
    content VARCHAR(50)
);

之后在项目中准备一个实体类,属性与数据库字段对应,用于封装对象。

以及创建一个接口类,先写一个获取全部信息的方法:

代码语言:javascript
复制
public interface EmployeeMapper {
    List<Employee> getAll();
}

因为Mybatis使用了Java动态代理直接通过接口调用对应方法,因此这里只需要写接口类即可,无需写实现类。

2)写配置

基于 MyBatis 的应用都需要一个配置文件,在其中配置 Configuration 实例,用于后续构建 SqlSessionFactoryBuilder 。而使用SqlSessionFactoryBuilder 可以构建出 SqlSessionFactory 实例,这是应用的核心。配置文件的样例在官方文档中也有给出:

代码语言:javascript
复制
<?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:///mybatis_db"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="mapper/firstMapper.xml"/>
  </mappers>
</configuration>

之后需要配置上SQL的映射文件,我们在资源目录下创建一个mapper.firstMapper.xml文件:

代码语言:javascript
复制
<?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="top.jtszt.mapper.EmployeeMapper">
    
    <select id="getAll" resultType="top.jtszt.entity.Employee">
        select * from t_employee
    </select>
</mapper>

3)测试

简单的配置已经完成了,现在就可以尝试获取查询结果了,这里写一个测试去获取查询结果:

代码语言:javascript
复制
public class EmployeeTest {

    SqlSessionFactory sqlSessionFactory;

    @Before
    public void initSqlSessionFactory(){
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(inputStream!=null){
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            }
        }
    }

    @Test
    public void test01() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            List<Employee> ls = mapper.getAll();
            System.out.println(ls);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

首先我们会通过Resources类的 getResourceAsStream 方法将配置文件加载成输入流,之后new一个 SqlSessionFactoryBuilder 对象,调用其 build 方法并传入配置文件输入流,生成一个sqlSessionFactory,看这名字也清楚,它就是用来造sqlSession的,它里面有一个openSession方法,调用返回一个SqlSession。

3、全局配置文件

在Mbatis的全局配置中 <configuration> 标签下可配置多个标签,每个标签代表一个属性,并且他们的配置是有顺序的,从上至下依次为:

1、properties 2、settings 3、typeAliases

4、typeHandlers 5、objectFactory

6、objectWrapperFactory 7、reflectorFactory

8、plugins 9、environments

10、databaseIdProvider 11、mappers

接下来详细介绍一些重要的标签的含义及使用。

1)properties

全局配置文件中 properties 属性的作用是定义全局配置变量,可以用它来加载外部化的 properties 配置文件。

①全局配置变量

首先定义两个全局配置变量,之后就可以在配置数据源的时候使用 ${key} 进行值绑定。

代码语言:javascript
复制
<properties>
    <property name="jdbc.user" value="root"/>
    <property name="jdbc.password" value="root"/>
</properties>
代码语言:javascript
复制
<dataSource type="POOLED">
    <property name="username" value="${jdbc.user}"/>
    <property name="password" value="${jdbc.password}"/>
    ...
</dataSource>
②加载外部配置

也可以使用properties来引入外部的配置文件,改变上面配置中数据源的硬编码,假设现在有一个外部配置文件为 db.properties

代码语言:javascript
复制
jdbc.username=root
jdbc.password=root
jdbc.jdbcUrl=jdbc:mysql:///mybatis_db
jdbc.driverClass=com.mysql.jdbc.Driver

使用properties配置外部配置,

代码语言:javascript
复制
<properties resource="db.properties"/>

之后就可以在配置中使用 ${key} 取出对应属性的值。

代码语言:javascript
复制
<dataSource type="POOLED">
    <property name="driver" value="${jdbc.driverClass}"/>
    <property name="url" value="${jdbc.jdbcUrl}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

2)setting

这里的配置主要影响了Mybatis的行为,说是最重要的配置也不为过,包括缓存、延迟加载、驼峰映射等配置,所有可配置的属性在官方文档中都有列举,这里只记录几个比较重要的。

①mapUnderscoreToCamelCase

设置为true之后即为开启驼峰命名规则映射,什么作用?

我们都知道在MySQL中字段的命名是可以使用下划线的,比如描述书名的字段可设置为 book_name ,而Java中的属性命名推荐是驼峰命名规则 bookName 。我们在使用 JDBC 进行查询的时候就可以体会到,如果正常查询该字段是无法封装到实体类中的,需要使用别名查询指定才可。

在Mybatis中只要我们开启了驼峰命名映射,那么Mybatis会帮我们自动封装,将字段名转换为符合Java命名规范的驼峰式命名。

代码语言:javascript
复制
<!-- 开启驼峰命名映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
②lazyLoadingEnabled

设置为true即为开启延迟加载,搭配缓存可以达到提升查询效率的作用。

代码语言:javascript
复制
<setting name="lazyLoadingEnabled" value="true"/>
③logImpl

用于设置日志提供商,不配置会自动查找,可用于多日志的情况下日志的选用。比如我们这里配置的是log4j的,那么可以显式地配置。

代码语言:javascript
复制
<setting name="logImpl" value="LOG4J"/>

3)typeAliases

该属性标签可用于设置多个类型别名,设置之后对于全类名可以只写类名,但是建议写全类名,比较清晰。可使用 typeAliase 标签对别名进行设置,其中 alias 属性为别名内容(不设置默认为类名) ,type 属性为要用别名替换的内容。

代码语言:javascript
复制
<typeAliases>
    <typeAlias alias="Employee" type="top.jtszt.bean.Employee"/>
</typeAliases>

也可以使用 package 标签进行批量别名设置, name属性为包名。设置之后被扫描的包中的实体类别名为类名的小驼峰形式。如果需要制定别名,可以在类上标注 @Alias("xx") 指定。

代码语言:javascript
复制
<package name="top.jtszt.bean"/>

Mybatis中别名不区分大小写。

4)typeHandlers

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

简单地说,我们在使用Mybatis的增删改查时可能有传入参数,那么传入的参数是Java类型,而到数据库中运行就要进行转换,那么这个参数传到预编译SQL之前的转换就是由类型处理器完成的。Mybatis中预提供一些类型处理器:

类型处理器

Java 类型

JDBC 类型

IntegerTypeHandler

java.lang.Integer, int

数据库兼容的 NUMERIC 或 INTEGER

LongTypeHandler

java.lang.Long, long

数据库兼容的 NUMERIC 或 BIGINT

FloatTypeHandler

java.lang.Float, float

数据库兼容的 NUMERIC 或 FLOAT

DoubleTypeHandler

java.lang.Double, double

数据库兼容的 NUMERIC 或 DOUBLE

...

除此之外也可以自定义typeHandler ,只需要实现 TypeHandler 接口, 或继承 BaseTypeHandler类, 并且将它映射到一个 JDBC 类型。其中的实现方法类似 PreparedStatement 的setObject方法,对类型进行转换或者其他处理。

现在我们有一个Info的实体类,包括title和content两个属性,而Employee表中的信息字段为varchar,只显示content内容,因此我们以此实现类型的转换。先写一个转换器类:

代码语言:javascript
复制
public class MyTypeHandler implements TypeHandler<Info> {
    @Override
    public void setParameter(PreparedStatement ps, int i, Info info, JdbcType jdbcType) throws SQLException {
        ps.setObject(i, info.getContent());
    }
    @Override
    public Info getResult(ResultSet rs, String s) throws SQLException {
        Info info = new Info();
        info.setContent(rs.getString(s));
        return info;
    }
    @Override
    public Info getResult(ResultSet rs, int i) throws SQLException {
        Info info = new Info();
        info.setContent(rs.getString(i));
        return info;
    }
    @Override
    public Info getResult(CallableStatement cs, int i) throws SQLException {
        Info info = new Info();
        info.setContent(cs.getString(i));
        return info;
    }
}

写完类型处理器类之后配置上它,这里的JavaType 指向的是Java类型,jdbcType指向的是需要转换为的jdbc类型:

代码语言:javascript
复制
<typeHandlers>
    <typeHandler handler="top.jtszt.handler.MyTypeHandler" javaType="top.jtszt.entity.Info" jdbcType="VARCHAR"/>
</typeHandlers>

这样我们再运行查询语句,就可以看见被封装好的info字段了。

5)environments

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。现实中可以将开发、测试和生产环境配上不同的配置(比如配置不同的数据库),我们可以在创建 SqlSessionFactory 的时候指定环境,如果不指定则为默认。在配置文件中可以配置多个环境,但默认的只能有一个。

代码语言:javascript
复制
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="url" value="${jdbc.url}"/>
                ...
            </dataSource>
        </environment>
        <environment id="production">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="url" value="${prod.url}"/>
                ...
            </dataSource>
        </environment>
    </environments>
①transactionManager

这里还涉及到 environment 中的一个子标签 <transactionManager> ,这个标签可以指定使用的事务管理器。在 MyBatis 中有两种类型的事务管理器(JDBC、MANAGED),JDBC没什么好说的,而配成MANAGED 则表示从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期,默认情况下它会关闭连接。

②dataSource

它配置的是数据源,可以有三种选择(UNPOOLED、POOLED、JNDI)。

UNPOOLED:数据源的实现会在每次请求时打开和关闭连接,也就是没有连接池;

POOLED:连接池方式的数据源,可以避免创建新连接的消耗;

JNDI:这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用。

而在后续使用Spring整合之后,我们会让Spring进行事务管理,因此这里可不配置数据源以及事务管理器。

6)databaseIdProvider

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。

如果想要配置针对多厂商数据库的支持,则需要在配置文件中加入databaseIdProvider定义:

代码语言:javascript
复制
<databaseIdProvider type="DB_VENDOR">
    <property name="MySQL" value="mysql"/>
    <property name="DB2" value="db2"/>
    <property name="Oracle" value="oracle" />
</databaseIdProvider>

这样配置以后,只要我们在映射文件中的标签上加上 databaseId 属性即可。

7)mappers

这个属性标签就是指向SQL映射文件的,下面有两个标签,<mapper><package> ,其中mapper标签的 resource 表示直接从类路径下取, class 表示从映射器接口实现类的全类名获取(如果对应映射的是一个接口,那么xml文件也应该在同个包下),url 表示从网络资源/磁盘路径下获取。package标签下的 name 表示将该包内的映射器接口实现全部注册为映射器(如果xml文件在resources目录下,那么需要在该目录下也创建一个同包名的包,将文件置于其中)。

代码语言:javascript
复制
<mappers>
    <mapper resource="mapper/EmployeeMapper.xml"/> 
    <mapper class="top.jtszt.mapper.EmployeeMapper"/>
    <package name="top.jtszt.mapper"/> 
</mappers>

4、SQL映射文件

在mapper文件中,最大的就是一个mapper标签,其中 namespace属性为命名空间,指向的是该映射文件对应的接口(对于哪个接口方法的映射/实现)。命名空间的作用是利用更长的全限定名来将不同的语句隔离开来,同时也实现了对于自定义的mapper接口的绑定。也就是说从命名空间中可以看到这个映射文件对应的接口类,并且这里的SQL语句可以依据命名空间隔离开。

代码语言:javascript
复制
<mapper namespace="top.jtszt.mapper.EmployeeMapper"></mapper>

接下来就可以了解这里面的子标签了,首先是增删改查标签。

1)select

它的作用就是对查询语句进行映射,基本形式为:

代码语言:javascript
复制
<select id="xx">SQL语句</select>
①标签属性

首先是这里的id,指的是“这个SQL语句是对哪个方法的实现”,它在当前命名空间必须是唯一的。

接下来我们重新写一个映射语句,表示 “根据id查询出员工信息”:

代码语言:javascript
复制
<select id="getById" parameterType="integer" useCache="true" timeout="5" statementType="PREPARED" resultType="top.jtszt.entity.Employee">
    select * from t_employee where id=#{id}
</select>

parameterType :表示的是传入参数的类型,实际上这个属性不用设置,Mybatis会帮我们推断出类型;•useCache:为true表示将结果保存至二级缓存中,当然它默认值就为true;•flushCache:执行后本地缓存与二级缓存都会被清空•timeout :设置在抛出异常之前驱动程序等待数据库返回请求结果的秒数,默认不设置;•statementType :设置底层使用的Statement类型,默认为PREPARED代表PreparedStatement;•databaseId:数据库厂商标识,这个属性就是搭配全局配置文件中的databaseIdProvider的;•resultType :设置结果集的类型,如果是返回集合,则应该填其中元素的类型;

这里说到的resultType还有一个与它类似的属性 resultMap ,它表示对于外部 resultMap 的命名引用。而resultMap是啥?

现在假设我们数据库表中存在一个passwd字段,而在实体类中我们定义属性为password,这时再进行查询一定会报错,因为Mybatis无法进行自动封装了。

此时就可以写一个resultMap来自定义规则(resultMap为mapper的子标签):

代码语言:javascript
复制
<resultMap id="myResultMap" type="top.jtszt.entity.Employee">
    <id property="id" column="id"/>
    <result property="ename" column="ename"/>
    <result property="gender" column="gender"/>
    <result property="email" column="email"/>
    <result property="info" column="content"/>
    <result property="password" column="passwd"/>
</resultMap>

写完自定义规则之后,再将select标签的resultType更换为resultMap,之后再查询就可:

代码语言:javascript
复制
<select id="getAll" resultMap="myResultMap">
    select * from t_employee
</select>

这就是select标签中的resultMap的作用,它和resultType是互斥的。

②传参几种情况

1.传入的参数只有一个:取值是 #{随意写} 都可以获取成功;2.传入的参数有多个(1):mybatis会将参数封装到一个map中,所以要取出参数必须按照默认根据索引(0, 1, ...)或者 param1, param2, ... 获取。这种按照位置传参的方式不推荐,只要位置一变就嗝屁了;3.传入的参数有多个(2):推荐在所映射的接口对应参数前加 @Param(参数名) 来指定封装的key,这样在取值时就可以直接使用 #{参数名} 获取。一般多个参数可先封装到类中,再作为参数传递;4.传入的参数是实体类:直接使用 #{属性名} 获取;5.传入的参数是map:直接使用 #{key} 获取。

代码语言:javascript
复制
// 假设一个将用户名和id传入,查询出员工信息的接口
Employee getEmpByInfo(@Param("id")Integer id, @Param("ename")String ename);

这里传入多个参数就是用到 @Param来对参数进行显式表示,之后在SQL映射文件直接获取即可。

③ #{ } 和 ${ }

对于获取参数有两种形式,{}不会预编译参数,只是简单拼接,因此它防不了sql注入。#{} 会预编译参数,可以防止sql注入。但是需要注意的是,如果传入的是表名以及order by后的参数值,那么必须用 {}。此外如果是对于属性的引用那么必须写

2) insert、update、delete

这三个标签就是用来表示增删改语句映射的,先看看他们的使用。这里需要注意的是在每次完成操作之后,都需要调用SqlSession的commit方法,将更改提交到数据库中。

①insert

假设现在需要插入一个员工信息,那么先写一个接口:

代码语言:javascript
复制
int insertEmployee(Employee employee);

再写映射文件:

代码语言:javascript
复制
<!-- 这里只插入两个个字段 -->
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO 
        t_employee
    SET 
        ename=#{ename},
        email=#{email}
</insert>

这里使用到的属性后面会分析,只需要清楚这里是实现插入功能即可。

②update

同样写接口:

代码语言:javascript
复制
int updateEmployee(Employee employee);

接着写映射文件:

代码语言:javascript
复制
<update id="updateEmployee">
    UPDATE
        t_employee
    SET
        ename=#{ename}
    WHERE
        id=#{id}
</update>
③delete

写接口:

代码语言:javascript
复制
int deleteEmployee(Integer id);

写映射文件:

代码语言:javascript
复制
<delete id="deleteEmployee">
    DELETE FROM
        t_employee
    WHERE
        id=#{id}
</delete>
④属性分析

由于他们的标签体中的属性大都相同,这里也就归并到一起说,并且只写出与select标签中不一致的属性,相同的就略过了。

flushCache:同样是清缓存,不同的是对于这三个标签,它默认为true,而select为false;•useGeneratedKeys:当设置为true时MyBatis会调用getGeneratedKeys 方法取出由数据库内部生成的主键(如自增id),默认值:false;•keyProperty:用于在insert、update语句中指定主键,MyBatis可以使用getGeneratedKeys的返回值或insert语句的selectKey子元素设置它的值;•keyColumn:用于在insert、update语句中指定主键列,在PostgreSQL等数据库中,当主键列不是表中的第一列的时候必须设置。

当我们的id是主键列并且自增时,可以设置useGeneratedKeys 为true以及 keyProperty 为id,之后可以不用传入id的值,Mybatis会自动帮我们把自增后的id填充上。

假设我们现在要插入一个员工的信息,那么我们可以不传入id的值:

代码语言:javascript
复制
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO t_employee(ename,gender,email,content,passwd)
    VALUES(#{ename},#{gender},#{email},#{info},#{password})
</insert>

由于设置了那两个属性,这里的id也是可以自动填充的。

对于不支持自增id的数据库,我们也可以先对id值进行设置,之后再插入:

代码语言:javascript
复制
<insert id="insertEmployee">
    <selectKey keyProperty="id" resultType="integer" order="BEFORE" >
        SELECT UNIX_TIMESTAMP(NOW())
    </selectKey>
    INSERT INTO t_employee(id,ename,gender,email,content,passwd)
    VALUES(#{id},#{ename},#{gender},#{email},#{info},#{password})
</insert>

这样同样不需要手动传入id值。

3)sql

这个标签是为了将可重用的sql片段抽取出来,方便复用,这里将需要查询的几个字段抽离出来:

代码语言:javascript
复制
<sql id="myStatement"> ${col}.id,${col}.ename,${col}.passwd </sql>

之后在增删改查语句中使用inclue语句引入:

代码语言:javascript
复制
<select id="getEmployeeById" resultMap="myResultMap">
    SELECT
    <include refid="myStatement">
        <property name="col" value="t_employee"/>    
    </include>
    FROM t_employee WHERE id=#{id}
</select>

4)resultMap

前面我们使用了这个标签去进行自定义封装,那么现在我们来仔细体会一下它的使用和作用。resultMap 标签中也有多个可用标签,他们也需要按照顺序配置(如果有的话):

1、constructor 2、id 3、result 4、association 5、collection 6、discriminator

①constructor

这个标签用于在实例化类时,将结果注入到构造方法中。它的使用需要有带参数的构造方法,虽然POJO中不允许带有参构造方法。

现在假设实体类中有这样一个构造方法:

代码语言:javascript
复制
public Employee(Integer id, String ename) {
    this.id = id;
    this.ename = ename;
}

我们需要在封装对象的时候利用该构造器注入值,那么就可以使用constructor标签:

代码语言:javascript
复制
<constructor>
    <idArg column="id" javaType="integer"/>
    <arg column="ename" javaType="String"/>
</constructor>

注意如果是参数为主键则使用的是idArg,column表示对应数据库字段。

也可以name属性搭配 @Param 注解对参数进行自定义:

代码语言:javascript
复制
public Employee(@Param("id") Integer id,@Param("empName") String ename) {
    this.id = id;
    this.ename = ename;
}
代码语言:javascript
复制
<constructor>
    <idArg column="id" javaType="integer" name="id"/>
    <arg column="ename" javaType="String" name="empName"/>
</constructor>
②id result

id 和 result 标签都将一个列的值映射到一个简单数据类型的属性或字段(非实体类包装),这两者的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。通常主键映射使用的是id,其他字段映射使用result

现在拉取我们之前的实例,这就是它的使用方法:

代码语言:javascript
复制
<resultMap id="myResultMap" type="top.jtszt.entity.Employee">
    <id property="id" column="id"/>
    <result property="ename" column="ename"/>
    <result property="gender" column="gender"/>
    <result property="email" column="email"/>
    <result property="info" column="content"/>
    <result property="password" column="passwd"/>
</resultMap>

其中有多个可配置的属性:

property:表示映射到的字段或属性,这里对应javabean中的属性名;

column:表示数据库中的列名或者是别名;

javaType:指定Java类型,如果映射到javabean中可以忽略此属性;

jdbcType:指定JDBC类型,面向 JDBC 编程可填;

typeHandler:指定后可覆盖默认的类型处理器。

③association

现在假设员工表中还有一个字段为 department_id ,而这个部门id是外键,对应的是部门表的id,现在我们需要将员工信息查询出来的同时,把部门信息也查询出来。

首先新建一个部门表:

代码语言:javascript
复制
CREATE TABLE t_department(
    id INT PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(50)
);

接着修改员工表,并写一些测试数据:

代码语言:javascript
复制
ALTER TABLE t_employee
    ADD COLUMN department_id INT(11) NULL 
        AFTER passwd, 
    ADD CONSTRAINT fk_depid FOREIGN KEY(department_id) 
        REFERENCES t_department(id); 

接着我们修改实体类,添加一个部门实体类,并修改员工实体类。

代码语言:javascript
复制
public class Department {
    private Integer id;
    private String name;
    ...
}
public class Employee {
    private Integer id;
    private String ename;
    private Integer gender;
    private String email;
    private Info info;
    private String password;
    private Department department;
    ...
}

现在我们改造一下查询语句,使其可以查询出员工表和部门表的所有信息:

代码语言:javascript
复制
<select id="getAll" resultMap="myResultMap">
    SELECT
        e.*,
        d.id dept_id,
        d.name dept_name
    FROM
        t_employee e
    LEFT JOIN
        t_department d
    ON
        e.`department_id`=d.`id`
</select>

此时如果要将department信息封装到employee对象中,就需要用到association:

代码语言:javascript
复制
<resultMap id="myResultMap" type="top.jtszt.entity.Employee">
    <!-- 其他配置省略 -->
    <association property="department" javaType="top.jtszt.entity.Department">
        <id column="dept_id" property="id"/>
        <result column="dept_name" property="name"/>
    </association>
</resultMap>

这里有两个属性说明一下:

property:表示映射到的结果,这里填的是employee对象的department属性;•javaType:表示映射到的javabean的全类名,这里填的是department全类名;

此外 association 标签下使用到的这两个标签和resultMap下的id和result表示的意义完全一致。

如果不想使用联合查询,也可以使用分步查询,执行多次sql。使用这种方法首先需要定义一个查询department的select映射,同样使用 association 标签:

代码语言:javascript
复制
<association property="department" select="top.jtszt.mapper.DepartmentMapper.getDepartmentById" column="department_id"/>

这里的 select 属性指向查询部门的映射(命名空间+映射id), column 属性指向employee表中的那个外键字段。这样配置以后Mybatis会将查出来的employee的department_id传入getDepartmentById 中,将返回的对象给department属性。

④collection

对于属性中有集合类型的可以使用此标签。现在我们在部门实体类中增加一个属性,用来表示其部门下的全部员工信息:

代码语言:javascript
复制
public class Department {
    private Integer id;
    private String name;
    private List<Employee> employeeList;
    ...
}

现在我们想在查询部门的时候把其员工信息也查询出来,就需要用到collection标签:

方式一:

代码语言:javascript
复制
<select id="getDepartmentById" resultMap="deptMap">
        SELECT
            e.*,
            d.id dept_id,
            d.name dept_name
        FROM
            t_employee e
        LEFT JOIN
            t_department d
        ON
            e.`department_id`=d.`id`
        WHERE 
            d.id=#{id}
</select>
<resultMap id="deptMap" type="top.jtszt.entity.Department">
    <id property="id" column="dept_id"/>
    <result property="name" column="dept_name"/>
    <collection property="employeeList" ofType="top.jtszt.entity.Employee">
        <id column="id" property="id"/>
        <result column="ename" property="ename"/>
        <result property="gender" column="gender"/>
        <result property="email" column="email"/>
        <result property="info" column="content"/>
        <result property="password" column="passwd"/>
    </collection>
</resultMap>

collection 标签的property 表示映射到的属性名,ofType 表示集合中元素的类型,而在collection标签体内的标签就都是查询employee对象时的resultMap内容了,为了简便还可以直接使用 resultMap 属性引入employee的mapper中的resultMap:

代码语言:javascript
复制
<resultMap id="deptMap" type="top.jtszt.entity.Department">
    <id property="id" column="dept_id"/>
    <result property="name" column="dept_name"/>
    <collection property="employeeList" ofType="top.jtszt.entity.Employee" 
                resultMap="top.jtszt.mapper.EmployeeMapper.myResultMap"/>
</resultMap>

方式二:

代码语言:javascript
复制
<select id="getDepartmentById" resultMap="deptMap">
        SELECT * FROM t_department WHERE id=#{id}
</select>
<select id="getEmployeeByDeptId" 
        resultMap="top.jtszt.mapper.EmployeeMapper.myResultMap">
    SELECT * FROM t_employee WHERE department_id=#{dept_id}
</select>

<resultMap id="deptMap" type="top.jtszt.entity.Department">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="employeeList" select="getEmployeeByDeptId" 
                column="id"/>
</resultMap>
⑤discriminator

有时一个SQL可能会返回多个不同的结果集,使用鉴别器(discriminator)元素可以应对这种情况,它类似于 Java 中的 switch 语句。

5、动态SQL

动态SQL就是可以根据不同条件进行SQL的拼接查询,比如现在有一个查询页面,可以按照单条件/多条件进行查询,那么后台的SQL语句就要求做到可根据传入条件数量拼接语句,这时动态SQL就很有用。实现动态SQL有几个标签:if、choose (when, otherwise) 、trim (where, set) 、foreach。

1)if&trim

假设我们现在需要从页面获取查询条件内容,之后写根据不为空的条件进行查询,那么就可以使用if和trim/where标签搭配,先看看和where的搭配。

先写一个接口:

代码语言:javascript
复制
List<Employee> getEmployeeByInfo(Employee employee);

再写映射文件:

代码语言:javascript
复制
<select id="getEmployeeByInfo" resultMap="myResultMap">
    SELECT * FROM t_employee
    <where>
        <if test=" id != null ">
            AND id=#{id}
        </if>
        <if test=" ename != null and ename.trim()!='' ">
            AND ename=#{ename}
        </if>
        <!-- 其他以此类推 -->
    </where>
</select>

当我们的employee对象是空的时候,可以查询出来所有结果(没有where子句);传入几个参数就按几个参数查,可见where标签的作用是帮我们处理where子句添加以及and去除的问题。而if标签则是根据test中的条件判断标签体是否保留,test判断语句可以使用上OGNL语句。

接下来看看if和trim的搭配,还是同样的接口,修改以下映射文件:

代码语言:javascript
复制
<select id="getEmployeeByInfo" resultMap="myResultMap">
    SELECT * FROM t_employee
    <trim prefix="WHERE" suffixOverrides="AND">
        <if test=" id != null ">
            id=#{id} AND
        </if>
        <if test=" ename != null and ename.trim()!='' ">
            ename=#{ename} AND
        </if>
    </trim>
</select>

trim标签可以有四个属性设置:prefix 表示在整个标签体之前加的内容; suffix 表示在整个标签之后加的内容;prefixOverrides 表示将标签体内首部的该内容去掉;suffixOverrides 表示将标签体内尾部的该内容去掉。

因此这里是代表在首部加上WHERE,而如果标签体尾部出现AND则去掉,这样也可以满足要求。

2)choose

这个就有点像switch...case了,其实就是实现条件的选择,我们还是假设对于员工信息的查询,但是现在需求变了,变成根据单条件查询了,哪个有就查哪个,还是借助刚才的接口,直接重新写映射文件即可。

代码语言:javascript
复制
<select id="getEmployeeByInfo" resultMap="myResultMap">
    SELECT * FROM t_employee
    <choose>
        <when test="id!=null">
            WHERE id=#{id}
        </when>
        <when test="ename!=null and ename.trim()!=null">
            WHERE ename=#{ename}
        </when>
        <otherwise></otherwise>
    </choose>
</select>

when即相当于case,test同样是判断条件,里面可写的内容与if的test一致;otherwise表示默认执行的内容,相当于default。

3)foreach

现在需求又变了,只有id字段作为查询字段,但是它可以传入多个值,需要根据这组值查出符合条件的信息列表。实际开发中也常见,例如查询指定几个日期订单信息等。

如果需要遍历传入的条件时就需要用到foreach,按照这个需求,我们先写一个接口:

代码语言:javascript
复制
List<Employee> getEmployeeByIds(@Param("ids")List<Integer> ids);

这里使用@Param 来自定义参数名,接着写映射文件:

代码语言:javascript
复制
<select id="getEmployeeByIds" resultMap="myResultMap">
    SELECT * FROM t_employee
    <trim prefix="WHERE" >
        id IN
        <foreach collection="ids" item="ids_item" 
                 separator="," open="(" close=")">
            ${ids_item}
        </foreach>
    </trim>
</select>

这里需要注意foreach的几个属性,其中collection 代表的是传入的集合的参数名,我们前面使用@Param 指定了;item 配一个变量名,表示每次遍历出的值;separator 表示每次遍历出来的值用什么风格,由于是in语句就用了逗号;open 表示遍历开始前的字符;close 表示遍历结束的字符。

4)set

在进行update操作时,使用上set标签可以减少一定的工作量。

代码语言:javascript
复制
<update id="updateEmployee">
    UPDATE t_employee
    <set>
        <if test="ename != null">ename=#{ename},</if>
        <if test="email != null">email=#{email},</if>
    </set>
    WHERE id=#{id}
</update>

在update中使用set标签,它会自动帮我们加上SET前缀,并且抹去最后一个逗号。

6、基于注解配置

前面使用的配置都是基于xml的配置,实际上Mybatis3之后还提供了基于注解的配置,但是这种配置方式的表达能力和灵活性十分有限,甚至可以说复杂SQL根据注解来进行配置简直是灾难,不过简单语句配置利用注解确实很方便。

使用注解进行配置时我们只需要保留全局配置文件即可,并提供包含查询方法定义的接口类。

代码语言:javascript
复制
public interface EmployeeMapperByAnnotation {
    Employee getEmployeeById(Integer id);

    int insertEmployee(Employee employee);

    int deleteEmployee(Integer id);

    int updateEmployee(Employee employee);

    List<Employee> getAll();
}

1)@Select

①基本查询

首先是用于查询的注解 @Select ,先看它的用法:

代码语言:javascript
复制
@Select("SELECT * FROM t_employee")
List<Employee> getAll();

这样就相当于在mapper配置文件中写了一个<select> ,这样是可以查询出基本字段的,但实际上那些我们之前在resultMap中定义过的复杂的映射是无法完成的。

②带参
代码语言:javascript
复制
@Select("SELECT * FROM t_employee WHERE id=#{id}")
Employee getEmployeeById(Integer id);

这样也可以完成根据id的查询。说到按条件查询,这里是固定条件的查询,可如果是动态SQL的编写,也就是说条件不确定的情况下,使用注解要去拼接 <script> 写成脚本的形式,这种方式太...了,这里也不写了,就提一嘴。

③结果集映射

上面我们也看到了,查询出来的有关自定义类型映射的都不能正确显示,在xml中我们使用<resultMap> 去配置,在注解中也可以进行配置:

代码语言:javascript
复制
@Select("SELECT * FROM t_employee")
@Results(id = "myMap", value = {
    @Result(id = true, property = "password", column = "passwd"),
    @Result(property = "info", column = "content")
})
List<Employee> getAll();

这里的 @Results 相当于 <resultMap> 标签,它的id属性即为对该结果集映射的标识,value相当于标签体。而@Result 就相当于 <result> ,当id为true时,这个注解也相当于 <id>

注解配置的结果集也可以复用,我们可以使用 @ResultMap 注解来引用:

代码语言:javascript
复制
@Select("SELECT * FROM t_employee WHERE id=#{id}")
@ResultMap("myMap")
Employee getEmployeeById(Integer id);

此外还有其他的一些注解可以和xml配置中的标签对应上,但Mybatis不建议使用注解完成复杂的SQL映射编写。

2)@Insert 、@Update、@Delete

先针对增删改注解写一个简单的实例:

代码语言:javascript
复制
//插入
@Insert("INSERT INTO t_employee SET ename=#{ename},email=#{email}")
int insertEmployee(Employee employee);

//更新
@Update("UPDATE t_employee SET ename=#{ename},email=#{email} WHERE id=#{id}")
int updateEmployee(Employee employee);

//删除
@Delete("DELETE FROM t_employee WHERE id=#{id}")
int deleteEmployee(Integer id);

之前我们还配置了id的自增,这里也可以使用,需要使用到 @Options 注解,这个注解可以填充大部分的属性,之后将useGeneratedKeys以及keyProperty补充上即可。

代码语言:javascript
复制
@Insert("INSERT INTO t_employee SET ename=#{ename},email=#{email}")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertEmployee(Employee employee);

这里再提一下 @Options 注解,里面可设置的属性实际上就等同于xml配置标签中的属性,需要使用到点进去方法查看即可。

3)Provider系列

Provider 型注解允许指定返回 SQL 语句的类和方法,以供运行时执行,这类注解有两个属性:type 代表一个提供 SQL 的类,method代表一个提供 SQL 的方法,这个方法需要有一个 String 类型的返回值,参数可以根据实际情况定义。

使用这个注解之前,我们先准备一个Provider类,针对查询所有信息写一个方法:

代码语言:javascript
复制
public class EmployeeProvider {
    public String getAll(){
        return null;
    }
}

然后将EmployeeMapper接口的注解修改为@SelectProvider:

代码语言:javascript
复制
@SelectProvider(type = EmployeeProvider.class, method = "getAll")
List<Employee> getAll();

这里的type指向我们自定义的Provider类,method指向对应的方法。之后就可以在getAll方法体内写内容了:

代码语言:javascript
复制
public String getAll(){
    SQL sql = new SQL();
    sql.SELECT("*").FROM("t_employee2");
    return sql.toString();
}

这里直接调用了MyBatis提供的 API 构建出 SQL ,正是由于使用上了API,因而这里对于动态SQL也是可以写的,多加几个if语句然后调用AND方法以及WHERE方法等就可以实现,尽管这种方式写出来的方法后续有变动需要重新编译。此外还有InsertProvider、UpdateProvider、DeleteProvider,这些的用法都跟上面的实例差不多。

7、缓存

Mybatis中提供了强大的事务性查询缓存机制,在这里缓存也分为一级缓存和二级缓存。

1)一级缓存

①基本使用

Mybatis中的一级缓存比较常用,并且它是默认开启的,一级缓存基于 SqlSession ,默认情况下它会自动开启事务,所以一级缓存会自动使用。

假设我们现在在同一个sqlSession中进行两次相同的查询,那么第二次查询是不会重新去数据库中查询的,因为第一次查询之后的结果被存储到了一级缓存中。

从日志我们可以看到,从第一次出结果到第二次出结果中间是不会有新的SQL查询发起的。

②一级缓存失效

一级缓存在遇到一些特定的情况也会失效,失效之后像上面那种情况就需要重新发SQL查询了。一级缓存失效的场景主要有:

•跨sqlSession:因为一级缓存本来就是基于sqlSession的,所以不同的sqlSession之间一级缓存肯定不能相通,因而也就会出现这种失效情况;•混入增删改:如果在两次查询之间有了增删改操作,那么一级缓存会失效,我们之前将这三个标签的flushCache属性时也看到了它默认是true;•调clearCache:sqlSession中有一个方法 clearCache, 调用它会清空一级缓存

下图为没有一级缓存的情况:

2)二级缓存

二级缓存即是全局作用域的缓存,Mybatis提供二级缓存的接口和实现,缓存的实现要求实体类实现Serializable接口,并且二级缓存只有在sqlSession关闭/提交之后才会生效。

在Mybatis中二级缓存是默认开启的,关闭需要在全局配置文件中进行操作:

代码语言:javascript
复制
<settings>
    <setting name="cacheEnabled" value="false"/>
</settings>

如果是xml配置则在需要使用二级缓存的mapper文件中写入cache标签,如果是基于注解配置的,就在mapper接口上标注 @CacheNamespace。这样即使我们使用两个不同的sqlSession执行相同的SQL,第二个也是刻意直接从缓存中取结果的。

这里关注一下cache标签中的几个属性:

eviction:表示缓存的回收(清除)策略,默认为LRU,可选项为FIFO/SOFT/WEAK;

type:缓存的实现,默认为本地缓存,整合第三方缓存时需要修改它;

size:缓存引用的数量,默认1024;

flushInterval:缓存定时清除时间,默认无;

readOnly:设置为true则查询后返回的对象为基于序列化的深拷贝,效率会降低但安全;

blocking:如果为true则当找不到对应数据时会一直阻塞直到有对应数据进来。

此外这里的缓存还可以整合第三方的缓存EhCache ,步骤就是导EhCache依赖,写EhCache 配置文件(官网copy即可),修改cache标签的type属性,这样就大功告成了,这里就不细写了。


参考资料:

•MyBatis 3.5.7官方文档[1]•玩转 MyBatis:深度解析与定制-LinkedBear-掘金小册[2]•MyBatis (JavaGuide)[3]•雷丰阳Spring、Spring MVC、MyBatis课程-bilibili[4]

相关链接

[1] MyBatis 3.5.7官方文档: https://mybatis.org/mybatis-3/zh/ [2] 玩转 MyBatis:深度解析与定制-LinkedBear-掘金小册: https://juejin.cn/book/6944917557878980638 [3] MyBatis (JavaGuide): https://snailclimb.gitee.io/javaguide-interview/#/./docs/e-2mybatis [4] 雷丰阳Spring、Spring MVC、MyBatis课程-bilibili: https://www.bilibili.com/video/BV1d4411g7tv?p=256

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java后端修炼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、概述
  • 2、简单配置
    • 1)环境准备
      • 2)写配置
        • 3)测试
        • 3、全局配置文件
          • 1)properties
            • ①全局配置变量
            • ②加载外部配置
          • 2)setting
            • ①mapUnderscoreToCamelCase
            • ②lazyLoadingEnabled
            • ③logImpl
          • 3)typeAliases
            • 4)typeHandlers
              • 5)environments
                • ①transactionManager
                • ②dataSource
              • 6)databaseIdProvider
                • 7)mappers
                • 4、SQL映射文件
                  • 1)select
                    • ①标签属性
                    • ②传参几种情况
                    • ③ #{ } 和 ${ }
                  • 2) insert、update、delete
                    • ①insert
                    • ②update
                    • ③delete
                    • ④属性分析
                  • 3)sql
                    • 4)resultMap
                      • ①constructor
                      • ②id result
                      • ③association
                      • ④collection
                      • ⑤discriminator
                  • 5、动态SQL
                    • 1)if&trim
                      • 2)choose
                        • 3)foreach
                          • 4)set
                          • 6、基于注解配置
                            • 1)@Select
                              • ①基本查询
                              • ②带参
                              • ③结果集映射
                            • 2)@Insert 、@Update、@Delete
                              • 3)Provider系列
                              • 7、缓存
                                • 1)一级缓存
                                  • ①基本使用
                                  • ②一级缓存失效
                                • 2)二级缓存
                                  • 相关链接
                                  相关产品与服务
                                  云数据库 SQL Server
                                  腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档