首页 > 其他 > 详细

MyBatis学习记录

时间:2021-04-22 15:43:19      阅读:19      评论:0      收藏:0      [点我收藏+]

MyBatis


1 简介

1.1 什么是MyBatis

官方文档

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

1.2 什么是持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 持久层,可以理解成数据 保存在 数据库或者 硬盘一类可以保存很长时间的设备里面,不像放在内存中那样断电就消失了,也就是把数据存在持久化设备上,mybatis就是持久层。
  • 数据库(Jdbc),io文件持久化。

1.3 什么是持久层

Dao层、Service层、Controller层、...

  • 完成持久化工作的代码块
  • 层界限十分明显

1.4 MyBatis特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射。
  • 提供对象关系映射标签,支持对象关系组建维护。
  • 提供xml标签,支持编写动态sql。

2 第一个MyBatis程序

2.1 创建mybatis的核心配置文件

<?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?userSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>
</configuration>

2.2 编写mybatis工具类

//sqlSessionFactory -> sqlSession
public class MyBatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            // 使用MyBatis第一步:获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

2.3 编写代码

  • 实体类

    package com.pojo;
    
    
    public class User {
    
        private int id;
        private String name;
        private String pwd;
    
        public User(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.pwd = pwd;
        }
    
        public int getId() {
            return id;
        }
    
        public String getName() {
            return name;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    }
    
  • Dao接口

    package com.dao;
    
    import com.pojo.User;
    
    import java.util.List;
    
    public interface UserDao {
        List<User> getUserList();
    }
    
  • 接口实现类:由原来的UserDaoImpl转为UserMapper

    <?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">
    <!--namespace=绑定一个对应的Dao/Mapper接口-->
    <mapper namespace="com.dao.UserDao">
        <select id="getUserList" resultType="com.pojo.User">
            select * from mybatis.user
        </select>
    </mapper>
    
  • 测试

    // UserDaoTest.java
    package com.dao;
    
    import com.pojo.User;
    import com.utils.MyBatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class UserDaoTest {
        @Test
        public void Test1() {
    
            SqlSession sqlSession = MyBatisUtils.getSqlSession();
    
            try {
                // 1 获得SqlSession对象
    
    
                // 2 方式一:getMapper 更推荐
                UserDao mapper = sqlSession.getMapper(UserDao.class);
                List<User> userList= mapper.getUserList();
    
                // 2 方式二:selectList
                //List<User> userList = sqlSession.selectList("com.dao.UserDao.getUserList");
    
                for (User user : userList) {
                    System.out.println(user);
                }
    
            } catch(Exception e) {
                e.printStackTrace();
            } finally {
                // 3 关闭SqlSession
                sqlSession.close();
            }
        }
    }
    

2.4 可能出现的问题

  1. org.apache.ibatis.binding.BindingException: Type interface com.dao.UserDao is not known to theMapperRegistry.

    解决方法:在MyBatis的核心配置文件中注册Mapper

  2. java . ang.ExceptionInInitializerError异常

    原因:maven由于它的约定大于配置,可能遇到写的配置文件无法被到处或者生效问题

    解决方法:

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
                <!--<filtering>true</filtering>-->
            </resource>
    
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
                <!--<filtering>true</filtering>-->
            </resource>
        </resources>
    </build>
    
  3. 绑定接口错误

  4. 方法名不对

  5. 返回类型不对

3 增删改查(CRUD)

3.1 namespace

namespace中的包名要和接口的包名一致!

3.2 select

查询语句

  • id: 就是对应的 namespace 中的方法名
  • resultTypr: SQL语句执行的返回值
  • parameterType: 参数类型
  1. 编写接口方法

    // 查询全部用户
        List<User> getUserList();
    
        //根据id查询用户
        User getUserById(int id);
    
  2. 编写对应的mapper中的SQL语句

    <select id="getUserList" resultType="com.pojo.User">
            select * from mybatis.user
        </select>
    
        <select id="getUserById" resultType="com.pojo.User" parameterType="int">
            select * from mybatis.user where id = #{id};
        </select>
    
  3. 测试

    @Test
    public void Test1() {
    
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
    
        try {
    
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            List<User> userList= mapper.getUserList();
    
            for (User user : userList) {
                System.out.println(user);
            }
    
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            // 3 关闭SqlSession
            sqlSession.close();
        }
    }
    
    @Test
    public void Test2() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            System.out.println(mapper.getUserById(1));
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    

3.2 insert

  1. 编写接口方法

    //插入数据
    int addUser(User user);
    
  2. 编写对应的mapper中的SQL语句

    <insert id="addUser" parameterType="com.pojo.User">
        insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
    </insert>
    
  3. 测试,注意增删改需要提交事务

    @Test
    //增删改需要提交事务
    //增
    public void Test3() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            int res = mapper.addUser(new User(4, "赵六", "1593857"));
            if(res > 0) {
                System.out.println("插入成功");
            }
            //提交事务
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    

?

3.3 update

  1. 编写接口方法

    //修改用户
    int updateUser(User user);
    
  2. 编写对应的mapper中的SQL语句

    <update id="updateUser" parameterType="com.pojo.User">
        update mybatis.user set name=#{name}, pwd=#{pwd} where id=#{id};
    </update>
    
  3. 测试,注意增删改需要提交事务

    @Test
    //增删改需要提交事务
    //改
    public void Test4() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            int res = mapper.updateUser(new User(4, "赵六", "15321564"));
            if(res > 0) {
                System.out.println("修改成功");
            }
            //提交事务
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    

3.4 delete

  1. 编写接口方法

    //删除用户
    int deleteUser(int id);
    
  2. 编写对应的mapper中的SQL语句

    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id=#{id};
    </delete>
    
  3. 测试,注意增删改需要提交事务

    @Test
    //增删改需要提交事务
    //删
    public void Test5() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            int res = mapper.deleteUser(4);
            if(res > 0) {
                System.out.println("删除成功");
            }
            //提交事务
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    

3.5 Map使用

若实体类或者数据库中的表、字段或者参数过多时,可以考虑使用Map,列名与属性名不一致的时候使用Map。

  1. 编写接口方法

    // 使用Map插入数据
    int addUserByMap(Map<String, Object> map);
    
  2. 编写对应的mapper中的SQL语句

    <insert id="addUserByMap" parameterType="map">
        insert into mybatis.user (id, name, pwd) values (#{userId}, #{userName}, #{userPwd});
    </insert>
    
  3. 测试

    @Test
    public void Test31() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("userId", 5);
            map.put("userName", "July");
            map.put("userPwd", "12312312");
            int res = mapper.addUserByMap(map);
            if(res > 0) {
                System.out.println("插入成功");
            }
            //提交事务
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    

3.6 模糊查询

  1. Java代码执行的时候,传递通配符(% %)

    List<User> userList = mapper.getUserLike("%李%");
    
  2. 在SQL拼接中使用通配符

    select * from mybatis.user where name like "%"#{value}"%"
    

4 配置

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

4.1 属性(properties)

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。(可以通过properties属性来实现引用配置文件。)

  1. 编写properties文件

    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8
    username=root
    password=root
    
  2. 引入外部配置文件

    <!--引入外部配置文件-->
    <properties resource="db.properties">
    	<!--同一字段优先使用外部配置文件-->    
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </properties>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    

4.2 环境配置(environments)

  • MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
  • 不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

environments 元素定义了如何配置环境。

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

注意:

  • 默认使用的环境 ID(比如:default="development")。
  • 每个 environment 元素定义的环境 ID(比如:id="development")。
  • 事务管理器的配置(比如:type="JDBC")。
  • 数据源的配置(比如:type="POOLED")。

4.2.1 事务管理器

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

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。(默认)
  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。(了解)
  • 如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

4.2.2 数据源

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

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

    • driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
    • url – 这是数据库的 JDBC URL 地址。
    • username – 登录数据库的用户名。
    • password – 登录数据库的密码。
    • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。
    • defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。
  2. 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 时适用)。
  3. JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:

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

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

    • env.encoding=UTF8

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

4.3 设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 true | false false (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled 是否允许单个语句返回多结果集(需要数据库驱动支持)。 true | false true
useColumnLabel 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 true | false true
useGeneratedKeys 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 true | false False
autoMappingBehavior 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 NONE, PARTIAL, FULL PARTIAL
autoMappingUnknownColumnBehavior 指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应。WARNING: 输出警告日志(‘org.apache.ibatis.session.AutoMappingUnknownColumnBehavior‘ 的日志等级必须设置为 WARN)。FAILING: 映射失败 (抛出 SqlSessionException)。 NONE, WARNING, FAILING NONE
defaultExecutorType 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 任意正整数 未设置 (null)
defaultFetchSize 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 任意正整数 未设置 (null)
defaultResultSetType 指定语句默认的滚动策略。(新增于 3.5.2) FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置) 未设置 (null)
safeRowBoundsEnabled 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 true | false False
safeResultHandlerEnabled 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。 true | false True
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true | false False
localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 SESSION | STATEMENT SESSION
jdbcTypeForNull 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 OTHER
lazyLoadTriggerMethods 指定对象的哪些方法触发一次延迟加载。 用逗号分隔的方法列表。 equals,clone,hashCode,toString
defaultScriptingLanguage 指定动态 SQL 生成使用的默认脚本语言。 一个类型别名或全限定类名。 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler 指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5) 一个类型别名或全限定类名。 org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 true | false false
returnInstanceForEmptyRow 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) true | false false
logPrefix 指定 MyBatis 增加到日志名称的前缀。 任何字符串 未设置
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置
proxyFactory 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 CGLIB | JAVASSIST JAVASSIST (MyBatis 3.3 以上)
vfsImpl 指定 VFS 的实现 自定义 VFS 的实现的类全限定名,以逗号分隔。 未设置
useActualParamName 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) true | false true
configurationFactory 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3) 一个类型别名或完全限定类名。 未设置
shrinkWhitespacesInSql 从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5) true | false false
defaultSqlProviderType Specifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type(or value) attribute on sql provider annotation(e.g. @SelectProvider), when these attribute was omitted. A type alias or fully qualified class name Not set

4.4 别名(typeAliases)

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

    <typeAliases>
      <typeAlias alias="Author" type="domain.blog.Author"/>
      <typeAlias alias="Blog" type="domain.blog.Blog"/>
      <typeAlias alias="Comment" type="domain.blog.Comment"/>
      <typeAlias alias="Post" type="domain.blog.Post"/>
      <typeAlias alias="Section" type="domain.blog.Section"/>
      <typeAlias alias="Tag" type="domain.blog.Tag"/>
    </typeAliases>
    
  2. 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean(扫描这个包的实体类,它的默认别名就是类名首字母小写),比如:

    <typeAliases>
      <package name="domain.blog"/>
    </typeAliases>
    
  3. 每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

    @Alias("author")
    public class Author {
        ...
    }
    
  4. 注意

    • 实体类比较少时,建议使用第一种;实体类比较多时,建议使用第二种。
    • 别名优先级:typeAlias > 注解 > package
  5. 一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

    | 别名 | 映射的类型 |
    | ---------- | ---------- |
    | _byte | byte |
    | _long | long |
    | _short | short |
    | _int | int |
    | _integer | int |
    | _double | double |
    | _float | float |
    | _boolean | boolean |
    | string | String |
    | byte | Byte |
    | long | Long |
    | short | Short |
    | int | Integer |
    | integer | Integer |
    | double | Double |
    | float | Float |
    | boolean | Boolean |
    | date | Date |
    | decimal | BigDecimal |
    | bigdecimal | BigDecimal |
    | object | Object |
    | map | Map |
    | hashmap | HashMap |
    | list | List |
    | arraylist | ArrayList |
    | collection | Collection |
    | iterator | Iterator |

4.5 其余配置

  • 类型处理器(typeHandlers)
  • 对象工厂(objectFactory)
  • 插件(plugins)
    • mybatis-generator-core
    • mybatis-plus
    • 通用mapper

4.6 映射器(mappers)

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。

  1. 方式一:推荐使用

    <mappers>
        <mapper resource="com/dao/UserMapper.xml"/>
    </mappers>
    
  2. 方式二:使用class文件绑定注册

    <mappers>
        <mapper class="com.dao.UserMapper"/>
    </mappers>
    

    注意点:

    • 接口和它的Mapper配置文件必须同名
    • 接口和它的Mapper配置文件必须在同一个包下
  3. 方式三:使用扫描包绑定注册

    <mappers>
        <mapper package="com.dao"/>
    </mappers>
    

    注意点:

    • 接口和它的Mapper配置文件必须同名
    • 接口和它的Mapper配置文件必须在同一个包下

5 作用域和生命周期

作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

5.1 SqlSessionFactoryBuilder

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

5.2 SqlSessionFactory

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

5.3 SqlSession

  • 每个线程都应该有它自己的 SqlSession 实例。

  • 相当于连接池的一个连接请求,需要开启和关闭。

  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

  • 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。

  • 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。

  • 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

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

6 结果映射

6.1 当属性名与字段名不一致时,查询结果会出现异常

public class User {

    private int id;
    private String name;
    private String password;
    
}
//查询结果 
//User{id=1, name=‘张三‘, pwd=‘null‘}

解决方法:

  • 起别名(不建议)

    <!--UserMapper.xml-->
    <select id="getUserById" resultType="com.pojo.User" parameterType="int">
        <!--select * from mybatis.user where id = #{id};-->
        select id, name, pwd as password from mybatis.user where id = #{id};
    </select>
    

6.2 ResultMap结果映射集

  • resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。

  • 实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。

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

  • 下面的语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 并不是一个很好的领域模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。

    <select id="selectUsers" resultType="map">
      select id, username, hashedPassword
      from some_table
      where id = #{id}
    </select>
    
  • ResultMap 的优秀之处——你完全可以不用显式地配置它们。对于6.1中的问题,采用ResultMap进行解决:

    <!-- 进行映射,将列映射到相应的属性上
    	type : 自定义规则的Java类型
    	id : 唯一id方便引用
     -->
    <resultMap id="userMap" type="com.pojo.User">
        <!-- 封装规则——只要写resultMap就建议把全部的映射规则都写上
    		id : 定义主键会底层有优化
    		column : 指定哪一列
     		property : 指定对应的javaBean属性
    		result : 普通列封装规则
    	-->
    <result column="pwd" property="password"></result>
    </resultMap>
    
    <select id="getUserById" resultMap="userMap">
        select * 
        from mybatis.user 
        where id = #{id};
    </select>
    

6.3 resultMap相关参数

  • constructor: 用于在实例化类时,注入结果到构造方法中

    • idArg: ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg: 将被注入到构造方法的一个普通结果
  • id: 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能

  • result: 注入到字段或 JavaBean 属性的普通结果

  • association: 一个复杂类型的关联;许多结果将包装成这种类型

    • 嵌套结果映射: 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection: 一个复杂类型的集合

    • 嵌套结果映射: 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator: 使用结果值来决定使用哪个resultMap

    • case: 基于某些值的结果映射
      • 嵌套结果映射 : case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射

6.4 关联查询

6.4.1 级联属性封装

<!--
场景一:
	查询Employee的同时查询员工对应的部门
	一个员工有与之对应的部门信息
	id last_name gender d_id dept_name
-->
<resultMap type="com.pojo.Employee" id="EmpMap1">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="gender" property="gender"/>
    <result column="did" property="dept.id"/>
    <result column="dept_name" property="dept.deptName"/>
</resultMap>

<!-- public Employee getEmpAndDept(int id)-->
<select id="getEmpAndDept" resultMap="EmpMap1">
    select e.id id, e.name name, e.gender gender, e.d_id d_id,
    d.id did, d.dept_name dept_name
    from employee e, dept d
    where e.d_id = d.id and e.id=#{id}
</select>

6.4.2 association定义关联对象封装规则

基于6.4.1中查询,更改resultMap

<!-- 使用association定义关联的单个对线的封装规则-->
<resultMap id="EmpMap2" type="com.pojo.Employee">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="gender" property="gender"/>
    <!-- association可以指定联合的javaBean对象
         property : 指定哪个属性是联合的对象
         javaType : 指定这个属性对象的类型[不能省略]
    -->
    <association property="dept" javaType="com.pojo.Dept">
        <id column="did" property="id"/>
        <result column="dept_name" property="deptName"/>
    </association>
</resultMap>

使用associtaion进行分布查询

<!-- 使用associtaion进行分布查询
	 1. 先按照员工id查询员工信息
	 2. 根据查询员工信息中的d_id值去部门表查出部门信息
	 3. 部门设置到员工中
-->
<!--EmpMapper.xml-->
<resultMap id="EmpMapByStep" type="com.pojo.Employee">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="gender" property="gender"/>
    <!-- association定义关联对象的封装规则
            select : 表明当前属性是调用select指定方法查出的结果
            column : 指定将哪一列的值传给这个方法
        -->
    <association property="dept" select="com.dao.DeptMapper.getDeptById" column="d_id"></association>
</resultMap>

<select id="getEmpDeptByStep" resultMap="EmpMapByStep">
    select * from Employee where id=#{id}
</select>

<!--DeptMapper.xml-->
<resultMap id="deptMap" type="com.pojo.Dept">
    <id column="id" property="id"/>
    <result column="dept_name" property="deptName" />
</resultMap>
<select id="getDeptById" resultMap="deptMap">
    select *
    from Dept
    where id=#{id}
</select>

分布查询&延迟加载

查询Employee对象的是想,都将一起查询出来。部门信息在我们使用的时候再去查询;分段查询的基础上加上两个配置。

<settings>
    <setting name="lazyLoadingEnabled" value="true" />
    <setting name="aggressiveLazyLoading" value="false" />
</settings>

6.4.3 collection嵌套结果集的方式,定义关联的集合类型元素的封装规则

<!--场景二:
        查询部门的时候将部门对应的所有员工信息也查询出来
    -->
    <!--
        collection嵌套结果集的方式,定义关联的集合类型元素的封装规则
    -->
    <resultMap id="deptMap1" type="com.pojo.Dept">
        <id column="did" property="id"/>
        <result column="dept_name" property="deptName" />
        <!--
            collection : 定义关联集合类型的属性封装规则
            ofType : 指定集合里面元素的类型
        -->
        <collection property="emps" ofType="com.pojo.Employee">
            <id column="eid" property="id" />
            <result column="name" property="name" />
            <result column="gender" property="gender" />
        </collection>
    </resultMap>

    <select id="getDeptByIdPlus" resultMap="deptMap1">
        select d.id did, d.dept_name dept_name, e.id eid, e.name name, e.gender gender
        from dept d
        left join employee e on d.id=e.d_id
        where d.id=#{id};
    </select>

分布查询&延迟加载

跟6.4.2 association的分布查询&延迟加载一样,设置lazyLoadingEnabled和aggressiveLazyLoading

<!-- DeptMapper.xml -->
<resultMap id="deptMap2" type="com.pojo.Dept">
    <id column="id" property="id" />
    <result column="dept" property="deptName" />
    <collection property="emps" select="com.dao.EmpMapper.getEmpByDeptId" column="id"></collection>
</resultMap>

<select id="getDeptByStep" resultMap="deptMap2">
    select * from dept where id=#{id}
</select>

<!-- EmpMapper.xml -->
<select id="getEmpByDeptId" resultType="com.pojo.Employee">
    select * from employee where d_id=#{id}
</select>

分布查询传递多列值和fetchType

将多列的值封装map传递

<!-- 
	将多列的值封装map传递:
	column="{key1=column1, key2=column2}"

	fetchType=‘lazy‘ : 表示使用延迟加载;
			- laze : 延迟
			- eager : 立即
-->

6.5 discriminator鉴别器(了解)

<discriminator javaType="">
    <case value=""></case>
</discriminator>

<!-- 
	鉴别器 mybatis可以使用discriminator判断某列的值
	以封装Employee为例:
		如果查出的是女生 : 将部门信息查询出来,否则不查询;
-->
<resultMap id="EmpDis" type="com.pojo.Employee">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="gender" property="gender"/>

    <!--
            column : 指定判定的列名
            javaType : 列值对应的java类型
        -->
    <discriminator javaType="string" column="gender">
        <!-- resultType 不能缺少。或者用resultMap-->
        <case value="女" resultType="com.pojo.Employee">
            <association property="dept" select="com.dao.DeptMapper.getDeptById" column="d_id"></association>
        </case>
    </discriminator>

</resultMap>

7 日志

7.1 日志工厂

如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手。

以往使用sout、debug,现在则使用日志工厂

  • logImpl

    常用设置:

    • SLF4J
    • LOG4J(掌握)
    • LOG4J2
    • JDK_LOGGING
    • COMMONS_LOGGING
    • STDOUT_LOGGING(掌握)
    • NO_LOGGING
  • 使用STDOUT_LOGGING标准日志输出

    在mybatis核心配置文件中,配置文件

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    

    技术分享图片

7.2 LOG4J

什么是LOG4J

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;
  • 我们也可以控制每一条日志的输出格式;
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
  • 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
  1. 导入依赖包

    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
  2. 配置 log4j.properties 文件

    # log4j.rootLogger日志输出类别和级别:只输出不低于该级别的日志信息DEBUG < INFO < WARN < ERROR < FATAL
    # WARN:日志级别     CONSOLE:输出位置自己定义的一个名字       logfile:输出位置自己定义的一个名字
    log4j.rootLogger=DEBUG,console,logfile
    
    #############
    # 输出到控制台
    #############
    
    # 配置CONSOLE输出到控制台
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target = System.out
    log4j.appender.console.Threshold = DEBUG
    # 配置CONSOLE设置为自定义布局模式
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    # 配置CONSOLE日志的输出格式  [frame] 2019-08-22 22:52:12,000  %r耗费毫秒数 %p日志的优先级 %t线程名 %C所属类名通常为全类名 %L代码中的行号 %x线程相关联的NDC %m日志 %n换行
    log4j.appender.console.layout.ConversionPattern=[frame] %d{yyyy-MM-dd HH:mm:ss} - %-4r %-5p [%t] [%C]:%L %m%n
    
    ################
    # 输出到日志文件中
    ################
    
    # 配置logfile输出到文件中 文件大小到达指定尺寸的时候产生新的日志文件
    log4j.appender.logfile=org.apache.log4j.RollingFileAppender
    # 保存编码格式
    log4j.appender.logfile.Encoding=UTF-8
    # 输出文件位置此为项目根目录下的logs文件夹中
    log4j.appender.logfile.File=./logs/root.log
    # 后缀可以是KB,MB,GB达到该大小后创建新的日志文件
    log4j.appender.logfile.MaxFileSize=10MB
    # 设置滚定文件的最大值3 指可以产生root.log.1、root.log.2、root.log.3和root.log四个日志文件
    log4j.appender.logfile.MaxBackupIndex=3
    log4j.appender.D.Threshold=DEBUG
    # 配置logfile为自定义布局模式
    log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
    log4j.appender.logfile.layout.ConversionPattern=[%-5p]-[%d{yyyy-MM-dd HH:mm:ss}] [%c] %m%n
    
    ################
    # 日志输出级别
    ################
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sql=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
    
  3. 配置LOG4J为日志的实现

    <settings>
        <setting name="logImpl" value="LOG4J"></setting>
    </settings>
    
  4. LOG4J的使用

    技术分享图片

  5. 简单使用

    import org.apache.ibatis.session.SqlSession;
    import org.apache.log4j.Logger;
    
    public class UserTest {
        static Logger logger = Logger.getLogger(UserDaoTest.class);
    
        @Test
        public void testLog4j() {
            logger.info("info:运行了testLog4j");
            logger.debug("debug:运行了testLog4j");
            logger.error("error:运行了testLog4j");
        }
    }
    

8 分页

为什么要分页

  • 减少数据的处理量

8.1 使用limit分页

#语法: select * from user limit startIndex, pageSize (or pageSize);只使用一个参数时,默认范围为[0, pageSize)
select * from user limit 0, 2;

8.2 使用Mybatis分页

  1. 编写接口方法

    List<User> getUserLimit(Map<String, Object> map);
    
  2. 编写对应的SQL语句

    <select id="getUserLimit" parameterType="map" resultType="User">
        select * from mybatis.user limit #{startIndex}, #{pageSize};
    </select>
    
  3. 测试

    @Test
    public void testLimit() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("startIndex", 0);
            map.put("pageSize", 2);
            List<User> userList = mapper.getUserLimit(map);
    
            for(User user : userList) {
                System.out.println(user);
            }
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    

8.3 RowBounds分页(了解)

  1. 编写接口方法

    List<User> getUserRowBounds();
    
  2. 编写对应的SQL语句

    <select id="getUserRowBounds" resultType="User">
        select * from mybatis.user;
    </select>
    
  3. 测试

    @Test
    public void getUserByRowBounds() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        try {
            RowBounds rowBounds = new RowBounds(0,2);
            List<User> userList = sqlSession.selectList("com.dao.UserMapper.getUserRowBounds", null, rowBounds);
    
            for(User user:userList) {
                System.out.println(user);
            }
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    

8.3 MyBatis分页插件——PageHelper(了解)

分页插件PageHelper

9 注解开发

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

  • 本质:反射机制实现
  • 原理:动态代理模式
  1. 编写接口方法
@Select("select * from user")
List<User> getUser();
  1. 核心配置文件绑定接口
<mappers>
    <mapper class="com.dao.UserMapper"/>
</mappers>
  1. 测试

10 mybatis运行原理

  1. 通过加载mybatis全局配置文件以及mapper映射文件初始化configuration对象
    和Executor对象(通过全局配置文件中的defaultExecutorType初始化);
  2. 创建一个defaultSqlSession对象,将configuration对象和Executor对象注入给
    defaulSqlSession对象中;
  3. defaulSqlSession通过getMapper()获取mapper接口的代理对象mapperProxy
    (mapperProxy中包含defaultSQLSession对象)
  4. 执行增删改查:
    1)通过defaulSqlSession中的属性Executor创建statementHandler对象;
    2)创建statementHandler对象的同时也创建parameterHandler和
    resultSetHandler;
    3) 通过parameterHandler设置预编译参数及参数值;
    4)调用statementHandler执行增删改查;
    5)通过resultsetHandler封装查询结果

11 动态SQL

11.1 什么是动态SQL

  • 动态 SQL 是 MyBatis 的强大特性之一。

  • 如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。

  • 利用动态 SQL,可以彻底摆脱这种痛苦。

  • 使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

  • 四种标签:

    • if
    • choose (when, otherwise)
    • trim (where, set)
    • foreach

11.2 if标签

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。

<!-- 查询员工 要求 : 携带了哪个字段查询条件就带上这个字段的值
	test : 判断表达式 (OGNL表达式)
-->
<select id="getEmpByConditionIf" resultType="com.pojo.Employee">
    select * from employee
    where
    <if test="id != 0">
        id=#{id}
    </if>
    <if test="name != null and name != &quot;&quot;">
        and name like #{name}
    </if>
</select>

判断表达式使用了OGNL

OGNL ( Object Graph Navigation Language ) 对象图导航语言,这是一种强大的表达式语言,通过它可以非常方便的来操作对象属性。类似于我们的EL , SpEL等。

  • 访问对象属性: person.name

  • 调用方法: person.getName()

  • 调用静态属性/方法:

    • @java.lang.Math@PI
    • @java.uil.UUlD@randomUUID()
  • 调用构造方法: new com.pojo.Employee(‘admin‘).name

  • 运算符: +, -, *, /, %

  • 逻辑运算符: in, not in, >, >=, <, <=, ==, !=

  • 注意: xmI中特殊符号如",>,<等这些都需要使用转义字符

11.2 trim(where, set)标签

如果查询的时候未携带某些条件可能SQL拼装会有问题,例如11.1中的查询不携带id。

解决方法:

  1. 给where 后面加上1=1,以后的条件都and xxx.
  2. MyBatis是用where标签来将所有的查询条件包括在内。MyBatis就会将where标签中拼装的SQL多出来的and或者or去掉,只会去掉开头的and或or
  3. 若and和or连接在末尾,则使用trim标签
<!-- 使用where标签-->
<select id="getEmpByConditionIf" resultType="com.pojo.Employee">
    select * from employee
    <where>
        <if test="id != 0">
            id=#{id} and
        </if>
        <if test="name != null and name != &quot;&quot;">
            name like #{name} and
        </if>
        <if test="gender == &quot;男&quot; or gender == &quot;女&quot;">
            gender=#{gender}
        </if>
    </where>
</select>

<!-- 使用trim标签
	prefix="" :
		前缀,trim标签中是整个字符串拼串后的结果。
		prefix给拼串后的整个字符加一个前缀
	prefixOverrides="" :
		前缀覆盖:去掉整个字符串前面多余的字符
	suffix="" :
 		后缀,给拼串后的整个字符加一个后缀
	suffixOverrides="" :
		后缀覆盖去掉整个字符串后面多余的字符
-->
<select id="getEmpByCondition" resultType="com.pojo.Employee">
    select * from employee
    <trim prefix="where" suffixOverrides="and | or">
        <if test="id != 0">
            id=#{id} and
        </if>
        <if test="name != null and name != &quot;&quot;">
            name like #{name} and
        </if>
        <if test="gender == &quot;男&quot; or gender == &quot;女&quot;">
            gender=#{gender}
        </if>
    </trim>
</select>

动态更新语句——set标签

<update id="updateEmpBySet">
    update employee
    <set>
        <if test="name != null">
            name=#{name},
        </if>
        <if test="gender != null">
            gender=#{gender}
        </if>
    </set>
    where id=#{id}
</update>

<!-- 使用trim标签 -->
<update id="updateEmpBySet">
    update employee
    <trim prefix="set" suffixOverrides=",">
        <if test="name != null">
            name=#{name},
        </if>
        <if test="gender != null">
            gender=#{gender},
        </if>
    </trim>
    where id=#{id}
</update>

简单总结:where标签封装查询语句,set标签封装更新语句

11.3 choose (when, otherwise)标签——分支选择

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

以11.1查询为例,将查询的条件改为“如果携带了id就用id查,如果携带了name,就用name查,如果携带了gender,就用gender查”

<!-- choose标签
	类似于Switch-case-default语句
	when就相当于case语句
	otherwise相当于default语句
 -->
<select id="getEmpByConditionChoose" resultType="com.pojo.Employee">
    select * from employee
    <where>
        <choose>
            <when test="id != 0">
                id=#{id}
            </when>
            <when test="name != null">
                name like #{name}
            </when>
            <otherwise>
                gender = "男"
            </otherwise>
        </choose>
    </where>
</select>

11.4 foreach标签

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)

<!--
    foreach:
        collection : 指定要遍历的集合
           list类型的参数会特殊处理封装在map中
        item : 将当前遍历出的元素赋值给指定变量
        separator : 每个元素之间的分隔符
        open : 遍历出所有结果拼接一个开始的字符
        close : 遍历出所有结果拼接一个结束的字符
        index : 索引。
            遍历list的时候index是索引,item就是当前值
            遍历map的时候index表示的是map的key,item就是key对应的值

        #{变量名} 就能取出变量的值也就是当前遍历出的元素
    -->
<select id="getEmpByForeach" resultType="com.pojo.Employee">
    select * from employee where id in
    <foreach collection="list" item="item_id" separator="," open="(" close=")">
        #{item_id}
    </foreach>

</select>

批量保存

<!-- int addEmps(@Param("list")List<Employee> empList);
 	@Param注解 : 给变量取别名
-->
<insert id="addEmps">
    insert into employee (id, name, gender, d_id)
    values
    <foreach collection="list" separator="," item="emp">
        (#{emp.id}, #{emp.name}, #{emp.gender}, #{emp.dept.id})
    </foreach>
</insert>

11.5 其他标签或参数

  1. _parameter(参数): 代表整个参数

    • 单个参数: -parameter就是这个参数
    • 参数会被封装为一个map,_parameter就是代表这个map
  2. _databaseId(参数): 用于多数据库支持,如果配置了databaseIdProvider标签。

    • _databaseId: 就代表当前数据库的别名
    <select id="getEmp" resultType="Employee">
        <if test="_databaseId == ‘mysql‘">
            select * from employee
        </if>
        <if test="_databaseId == ‘oracle‘">
            select * from employee
        </if>
    </select>
    

    ?

  3. bind(标签)

    bind元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文

    <select id="getEmp" resultType="Employee">
        <bind name="_name" value="‘%‘ + _parameter.name + ‘%‘" />
        select * from employee
        where name like #{_name}
    </select>
    
  4. sql(标签)

    抽取可重用的sql片段。方便后面引用

    <sql id="insertColumn">
        id, name, gender, d_id
    </sql>
    
    <insert id="addEmp">
        insert into employee(<include refid="insertColumn"></include>) values (...)
    </insert>
    

12 缓存机制

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。

MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存。

  1. 默认情况下﹐只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
  2. 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  3. 为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

12.1 一级缓存

与数据库同一次回话期间查询到的数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,不需要再去数据库查询。

EmpMapper.xml

<select id="getEmpById" resultType="com.pojo.Employee">
    select * from employee
    where id=#{id}
</select>

EmpMapperTest.java

@Test
public void testGetEmpByConditionIf() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    try {
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Employee emp1 = mapper.getEmpById(1);
        System.out.println(emp1);
        Employee emp2 = mapper.getEmpById(1);
        System.out.println(emp2);
        System.out.println(emp1 == emp2);

    } catch(Exception e) {
        e.printStackTrace();
    } finally {
        sqlSession.close();
    }
}

运行结果:

技术分享图片

sqlSession级别的缓存。一级缓存是一直开启的。也就是说一个sqlSession有一个一级缓存,不同之间的一级缓存不能共享

一级缓存失效情况(没有使用到当前一级缓存的情况,还需要向数据库发送查询):

  1. sqlSession不同。
  2. sqlSession相同,查询条件不同。(当前一级缓存中还没有这个数据)
  3. sqlSession相同,两次查询之间执行了增删改。(这次增删改可能对当前数据有影响)
  4. sqlSession相同,手动清除了一级缓存。(缓存清空)

12.2 二级缓存(全局缓存)

基于namespace级别缓存,一个namespace对应一个缓存。

工作机制:

  1. 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中
  2. 如果会话关闭,一级缓存中的数据会被保存到二级缓存中。新的会话查询信息,就可以参照二级缓存。
  3. 不同namespace查出的数据会放在自己对应的缓存中

使用:

  1. 开启全局二级缓存配置:

  2. 去mapeer.xml中配置使用二级缓存。

    <!--
        eviction: 缓存的回收策略
            LRU - 最近最少使用的: 移除最长时间不被使用的对象。
            FIFO - 先进先出: 按对象进入缓存的顺序来移除它们。
            SOFT - 软引用: 移除基于垃圾回收器状态和软引用规则的对象。
            WEAK - 弱引用: 更积极地移除基于垃圾收集器状态和弱引用规则的对象。
            默认的是LRU。
        flushInterval: 缓存刷新间隔
            缓存多长时间清空一次,默认不清空,设置一个毫秒值
        readOnly: 是否只读
            true: 只读,mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
                mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
            false: 非只读,mybatis觉得获取的数据可能会被修改
                mybatis会用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
        size: 缓存存放多少元素
        type: 指定自定义缓存的全类名。实现Cache接口即可
    -->
    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="false"></cache>
    
  3. 我们的POJO中的实体类需要实现序列化Serializable接口(若cache标签中的readOnly设置为true则不需要)。

    //Employee.java实体类
    public class Employee implements Serializable {
        private int id;
        private String name;
        private String gender;
    }
    
    //测试类
    @Test
        public void testSecendCache() {
            SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
            SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
            try {
                EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
                EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
    
                Employee emp1 = mapper1.getEmpById(1);
                System.out.println(emp1 + "******1******");
                sqlSession1.close();
    
                Employee emp2 = mapper2.getEmpById(1);
                System.out.println(emp2 + "******2******");
                System.out.println(emp1 == emp2);
    
            } catch(Exception e) {
                e.printStackTrace();
            } finally {
    
                sqlSession2.close();
            }
        }
    }
    

    运行结果

    技术分享图片

效果:数据会从二级缓存中获取

  • 查出的数据都会被默认先放在一级缓存中
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存

MyBatis学习记录

原文:https://www.cnblogs.com/icehapi/p/14688594.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!