MyBatis学习

MyBatis

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

Github:https://github.com/mybatis/mybatis-3

Maven仓库:

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

 

1. 简介

1.1 什么是MyBatis

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。

  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

1.2 持久化、持久层

持久化

持久化是将程序数据在持久状态和瞬时状态间转换的机制。

  • 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。

  • JDBC就是一种持久化机制;文件IO也是一种持久化机制。

  • 在生活中:将鲜肉冷藏,吃的时候再解冻的方法;将水果做成罐头的方法。

为什么需要持久化服务呢?那是由于内存本身的缺陷引起的

  • 内存断电后数据会丢失,但有一些对象是无论如何都不能丢失的,比如银行账号等,遗憾的是,人们还无法保证内存永不掉电。

  • 内存过于昂贵,与硬盘、光盘等外存相比,内存的价格要高2~3个数量级,而且维持成本也高,至少需要一直供电吧。所以即使对象不需要永久保存,也会因为内存的容量限制不能一直呆在内存中,需要持久化来缓存到外存。

持久层

  • 完成持久化工作的代码块。 --> dao层(DAO (Data Access Object) 数据访问对象)

  • 大多数情况下特别是企业级应用,数据持久化往往也就意味着将内存中的数据保存到磁盘上加以固化,而持久化的实现过程则大多通过各种关系数据库来完成。

  • 不过这里有一个字需要特别强调,也就是所谓的“层”。对于应用系统而言,数据持久功能大多是必不可少的组成部分。也就是说,我们的系统中,已经天然的具备了“持久层”概念?也许是,但也许实际情况并非如此。之所以要独立出一个“持久层”的概念,而不是“持久模块”,“持久单元”,也就意味着,我们的系统架构中,应该有一个相对独立的逻辑层面,专注于数据持久化逻辑的实现.

  • 与系统其他部分相对而言,这个层面应该具有一个较为清晰和严格的逻辑边界。(即就是用来操作数据库存在的!)

1.3 MyBatis

  • Mybatis就是帮助程序猿将数据存入数据库中,和从数据库中取数据。

  • 传统的jdbc操作,有很多重复代码块。比如:数据取出时的封装,数据库的建立连接等等...,通过框架可以减少重复代码,提高开发效率。

  • MyBatis 是一个半自动化的ORM框架 (Object Relationship Mapping) -->对象关系映射

  • 所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单。

MyBatis的优点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以了,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。

  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

  • 提供xml标签,支持编写动态sql。

  • .......

2. 第一个MyBatis程序

  1. 搭建实验数据库;

    CREATE DATABASE `mybatis`;
     
    USE `mybatis`;
     
    DROP TABLE IF EXISTS `user`;
     
    CREATE TABLE `user` (
      `id` INT(20) NOT NULL PRIMARY KEY,
      `name` VARCHAR(30) DEFAULT NULL,
      `pwd` VARCHAR(30) DEFAULT NULL
    ) ENGINE=INNODB DEFAULT CHARSET=utf8;
     
    INSERT INTO `user`(`id`,`name`,`pwd`)
    VALUES (1,'Alice','123456'),(2,'Bob','abcdef'),(3,'Tony','987654');
  2. 新建一个普通Maven项目,删除src文件夹,以此作为所有项目的父项目;

  3. 导入Maven依赖;

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion><!--父项目-->
        <groupId>com.wang</groupId>
        <artifactId>MyBatis-project</artifactId>
        <packaging>pom</packaging>
        <version>1.0-SNAPSHOT</version>
        <modules>
            <module>mybatis-01</module>
        </modules><dependencies>
            <!--mybatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.6</version>
            </dependency>
            <!--mysql-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <!--Junit-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
        </dependencies><!--在build中配置resources以防止资源导出到target文件失败的问题-->
        <build>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
            </resources>
        </build></project>
  4. 在父项目中创建Maven子项目(mybatis-01);

  5. 编写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?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments><!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
        <mappers>
            <mapper resource="com/wang/dao/UserMapper.xml"/>
        </mappers>
    </configuration>
  6. 编写mybatis工具类;

    package com.wang.utils;
    ​
    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 java.io.IOException;
    import java.io.InputStream;
    ​
    public class MybatisUtils {
        private static SqlSessionFactory sqlSessionFactory;
    ​
        static {
            try {
                // 获取sqlSessionFactory对象(使用Mybatis第一步)
                String resource = "mybatis-config.xml";
                InputStream resourceAsStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    ​
        // 从 SqlSessionFactory 获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
        public static SqlSession getSqlSession(){
            return sqlSessionFactory.openSession();
        }
    }
  7. 创建实体类;

    package com.wang.pojo;
    ​
    public class User {
        private int id;
        private String name;
        private String pwd;
    ​
        public User() {
        }
    ​
        public User(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.pwd = pwd;
        }
    ​
        public int getId() {
            return id;
        }
    ​
        public void setId(int id) {
            this.id = id;
        }
    ​
        public String getName() {
            return name;
        }
    ​
        public void setName(String name) {
            this.name = name;
        }
    ​
        public String getPwd() {
            return pwd;
        }
    ​
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    ​
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    '}';
        }
    }
  8. 编写Dao接口;

    package com.wang.dao;
    ​
    import com.wang.pojo.User;
    ​
    import java.util.List;
    ​
    public interface UserDao {
        List<User> getUserList();
    }
  9. Dao接口实现类,由原来的UserDaoImpl转换为一个Mapper配置文件;

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace绑定一个Dao/Mapper接口,相当于接口的实现类-->
    <mapper namespace="com.wang.dao.UserDao">
        <select id="getUserList" resultType="com.wang.pojo.User">
            select * from mybatis.user
        </select>
    </mapper>

    注:namespace中的包名要和Dao/Mapper接口的包名一致。

  10. Junit测试。

     

     

     

注:

报错:org.apache.ibatis.binding.BindingException: Type interface com.wang.dao.UserDao is not known to the MapperRegistry.

解决:每一个Mapper.xml都需要在MyBatis核心配置文件中注册。

报错:Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/wang/dao/UserMapper.xml

解决:配置文件没有导出到target文件夹,需要在pom.xml中添加以下配置。

<!--在build中配置resources以防止资源导出到target文件失败的问题-->
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

修改完后可能还会出现1字节utf-8报错,将IDEA中Setting->Editor->File Encodings下的Global Encoding、Project Encoding和Default encoding for properties files都修改为UTF-8,然后把mybatis-config.xml中的useSSL修改为false。

3. CRUD

3.1 SELECT

查询语句:

  • id:对应的namespace中的方法名;

  • resultType : Sql语句执行的返回值;

  • parameterType : 对应的namespace中的方法的参数类型;

  1. 编写接口(UserDao.java)

    package com.wang.dao;
    
    import com.wang.pojo.User;
    
    import java.util.List;
    
    public interface UserDao {
        // 根据id查询用户
        List<User> getUserById(int id);
    }
  2. 编写对应的mapper中的sql语句(UserMapper.xml)

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <!--namespace绑定一个Dao/Mapper接口,相当于接口的实现类-->
    <mapper namespace="com.wang.dao.UserDao">
        <!--根据ID查询用户-->
        <select id="getUserById" parameterType="int" resultType="com.wang.pojo.User">
            select * from mybatis.user where id = #{id}
        </select>
    </mapper>
  3. 测试(UserDaoTest.java)

    package com.wang.dao;
    
    import com.wang.pojo.User;
    import com.wang.utils.MybatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class UserDaoTest {
        @Test
        public void testSelectUserById(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
    
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            List<User> userById = mapper.getUserById(1);
            System.out.println(userById);
    
            sqlSession.close();
        }
    }

3.2 INSERT

  1. 编写接口

    // 插入一个用户
    int insertUser(User user);
  2. 编写sql语句

    <!--插入-->
    <insert id="insertUser" parameterType="com.wang.pojo.User">
        insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd});
    </insert>
  3. 测试

    @Test
    public void testInsertUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        int res = mapper.insertUser(new User(4, "David", "111222"));
        if (res > 0){
            System.out.println("Insert Successfully!");
        }
    
        // 提交事务
        sqlSession.commit();
        sqlSession.close();
    }

3.3 UPDATE

  1. 编写接口

    // 更新用户数据
    int updateUser(User user);
  2. 编写sql语句

    <!--更新-->
    <update id="updateUser" parameterType="com.wang.pojo.User">
        update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id};
    </update>
  3. 测试

    @Test
    public void testUpdateUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        mapper.updateUser(new User(4, "JungKook", "222333"));
    
        sqlSession.commit();
        sqlSession.close();
    }

3.4 DELETE

  1. 编写接口

    // 删除一个用户
    int deleteUser(int id);
  2. 编写sql语句

    <!--删除-->
    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id=#{id};
    </delete>
  3. 测试

    @Test
    public void deleteUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        mapper.deleteUser(4);
    
        sqlSession.commit();
        sqlSession.close();
    }

注:增、删、改需要提交事务,不然无法持久化到数据库!

3.5 map

当实体类的参数,或者数据库中表的字段过多时,应该考虑使用Map!

  1. 编写接口

    // 使用map查询用户
    List<User> getUserByMap(Map<String, Object> map);
  2. 编写SQL语句

    <!--使用map查询用户-->
    <select id="getUserByMap" parameterType="map" resultType="com.wang.pojo.User">
        select * from mybatis.user where id = #{userId} and name = #{userName};
    </select>
  3. 测试

    @Test
    public void testSelectUserByMap(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("userId", 1);
        map.put("userName", "Alice");
        List<User> userByMap = mapper.getUserByMap(map);
        for (User user :
                userByMap) {
            System.out.println(user);
        }
    
        sqlSession.close();
    }

Map传递参数,直接在sql中取出key即可(parameterType=“map”) ;对象传递参数,直接在sql中取出对象的属性即可(parameterType=“Object”)。

只有一个基本类型参数的情况下,可以直接在sql中取到;多个参数用Map , 或者注解!

3.6 模糊查询

  1. Java代码中传递通配符

    List<User> userByLike = mapper.getUserByLike("%T%");
  2. SQL语句中使用通配符

    <select id="getUserByLike" parameterType="String" resultType="com.wang.pojo.User">
        select * from mybatis.user where name like concat('%', #{value}, '%');
    </select>

4. 配置解析

核心配置文件(查看官方文档)

  • mybatis-config.xml -> 系统核心配置文件

  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。

  • 能配置的内容如下:

    configuration(配置)
        properties(属性)
        settings(设置)
        typeAliases(类型别名)
        typeHandlers(类型处理器)
        objectFactory(对象工厂)
        plugins(插件)
        environments(环境配置)
            environment(环境变量)
                transactionManager(事务管理器)
                dataSource(数据源)
        databaseIdProvider(数据库厂商标识)
        mappers(映射器)
    <!-- 注意元素节点的顺序!顺序不对会报错 -->

4.1 环境配置 - environments

配置MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定),不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

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

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

例:

<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?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </dataSource>
    </environment>
</environments>

4.2 属性 - properties

数据库这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

  1. 编写一个配置文件(db.properties)

    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
    username=root
    password=123456
  2. 在核心配置文件中引入

    <!--核心配置文件-->
    <configuration>
        <!--引入外部配置文件-->
        <properties resource="db.properties">
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </properties>
    
        <environments default="test">
            <environment id="test">
                <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>
    </configuration>
  • 可以直接引入外部文件,也可以在properties中增加一些属性配置;

  • 如果两个文件有同一个字段,优先使用外部配置文件的。

4.3 设置 - settings

MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

  • 懒加载

  • 日志实现

  • 缓存开启关闭

示例:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

4.4 类型别名 - typeAliases

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

方式一:直接对类的全限定名设置别名

<typeAliases>
    <typeAlias type="com.wang.pojo.User" alias="user"/>
</typeAliases>

当这样配置时,user可以用在任何使用com.kuang.pojo.User的地方,如:

<mapper namespace="com.wang.dao.UserMapper">
    <select id="getUserList" resultType="user">
        select * from mybatis.user
    </select>
</mapper>

方式二:指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean

<typeAliases>
    <package name="com.wang.pojo"/>
</typeAliases>

每一个在包 com.wang.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.wang.pojo.User 的别名为user;若有注解,则别名为其注解值,com.wang.pojo.User 的别名为hello。

import org.apache.ibatis.type.Alias;

@Alias("hello")
public class User {
    private int id;
    private String name;
    private String pwd;
}

4.5 映射器 - mappers

  • 映射器 : 定义映射SQL语句文件

  • 既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。

方式一:使用相对于类路径的资源引用

<!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
<mappers>
    <mapper resource="com/wang/dao/UserMapper.xml"/>
</mappers>

方式二:使用完全限定资源定位符(URL),不推荐

<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>

方式三:使用映射器接口实现类的完全限定类名,需要配置文件名称和接口名称一致,并且位于同一目录下

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

方式四:将包内的映射器接口实现全部注册为映射器,但是需要配置文件名称和接口名称一致,并且位于同一目录下

<mappers>
    <package name="com.wang.dao"/>
</mappers>

4.6 其它配置(了解)

类型处理器

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

  • 你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。

对象工厂

  • MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。

  • 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过有参构造方法来实例化。

  • 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。

plugins 插件

  • mybatis-generator-core

  • mybatis-plus

  • 通用mapper

数据库厂商标识

4.7 作用域和生命周期

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

SqlSessionFactoryBuilder

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

SqlSessionFactory

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

SqlSession

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

5. 属性名和字段名不一致问题

实体类属性名:id,name,password;数据库字段名:id,name,pwd

查询结果:[User{id=1, name='Alice', password='null'}],没有对应的password字段,因而查询出来为null。

解决:

  • 方案一:在SQL语句中使用别名

    <!--根据ID查询用户-->
    <select id="getUserById" parameterType="int" resultType="com.wang.pojo.User">
        select id,name,pwd as password from mybatis.user where id = #{id}
    </select>

    查询结果:[User{id=1, name='Alice', password='123456'}]

  • 方案二:使用结果映射(resultMap),推荐

    <resultMap id="userMap" type="com.wang.pojo.User">
        <!--column数据库中的字段,property实体类中的属性-->
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="pwd" property="password"/>
    </resultMap>
    
    <!--根据ID查询用户-->
    <select id="getUserById" parameterType="int" resultMap="userMap">
        select id,name,pwd from mybatis.user where id = #{id}
    </select>

    查询结果:[User{id=1, name='Alice', password='123456'}]

ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。

6. 日志工厂(logImpl)

Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

  • SLF4J

  • LOG4J 【掌握】

  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING 【掌握】

  • NO_LOGGING

STDOUT_LOGGING

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

部分结果:

Opening JDBC Connection
Created connection 1514160588.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5a4041cc]
==>  Preparing: select id,name,pwd from mybatis.user where id = ?
==> Parameters: 1(Integer)
<==    Columns: id, name, pwd
<==        Row: 1, Alice, 123456
<==      Total: 1
[User{id=1, name='Alice', password='123456'}]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5a4041cc]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5a4041cc]
Returned connection 1514160588 to pool.

LOG4J

  1. 添加依赖,导入log4j包;

    <dependencies>
        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
  2. 编写配置文件(log4j.properties);

    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger=DEBUG,console,file
    
    #控制台输出的相关设置
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target = System.out
    log4j.appender.console.Threshold=DEBUG
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
    
    #文件输出的相关设置
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/log4j.log
    log4j.appender.file.MaxFileSize=10mb
    log4j.appender.file.Threshold=DEBUG
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%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. setting设置日志类型;

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
  4. 测试程序中使用log4j输出日志;

    package com.wang.dao;
    
    import com.wang.pojo.User;
    import com.wang.utils.MybatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.log4j.Logger;
    import org.junit.Test;
    
    import java.util.List;
    
    public class UserDaoTest {
        static Logger logger = Logger.getLogger(UserDaoTest.class);
    
        @Test
        public void testSelectUserById(){
            logger.info("info:进入selectUser方法");
            logger.debug("debug:进入selectUser方法");
            logger.error("error: 进入selectUser方法");
    
            SqlSession sqlSession = MybatisUtils.getSqlSession();
    
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userById = mapper.getUserById(1);
            System.out.println(userById);
    
            sqlSession.close();
        }
    }
  5. 测试。

    控制台输出:

    [com.wang.dao.UserDaoTest]-info:进入selectUser方法
    [com.wang.dao.UserDaoTest]-debug:进入selectUser方法
    [com.wang.dao.UserDaoTest]-error: 进入selectUser方法
    [org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 112619572.
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b67034]
    [com.wang.dao.UserMapper.getUserById]-==>  Preparing: select id,name,pwd from mybatis.user where id = ?
    [com.wang.dao.UserMapper.getUserById]-==> Parameters: 1(Integer)
    [com.wang.dao.UserMapper.getUserById]-<==      Total: 1
    [User{id=1, name='Alice', password='123456'}]
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b67034]
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6b67034]
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 112619572 to pool.

    log文件:

     

     

     

7. 分页

分页原因:在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。

7.1 limit分页

  1. 编写mapper接口,参数为map;

    // limit分页
    List<User> getUserByLimit(Map<String, Integer> map);
  2. 编写mapper文件(UserMapper.xml);

    <!--limit分页,属性名和字段名不一致,因此pwd使用别名password-->
    <select id="getUserByLimit" parameterType="map" resultType="com.wang.pojo.User">
        select id,name,pwd as password from mybatis.user limit #{startIndex},#{pageSize};
    </select>
  3. 测试。

    @Test
    public void testGetUserByLimit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        map.put("startIndex", 1);
        map.put("pageSize", 2);
        List<User> userByLimit = mapper.getUserByLimit(map);
        for (User user : userByLimit) {
            System.out.println(user);
        }
        sqlSession.close();
    }
    // 结果:
    User{id=2, name='Bob', password='abcdef'}
    User{id=3, name='Tony', password='987654'}

7.2 RowBounds分页(了解)

我们除了使用Limit在SQL层面实现分页,也可以使用RowBounds在Java代码层面实现分页。

  1. 编写接口;

    // RowBounds分页
    List<User> getUserByRowBounds(Map<String, Integer> map);
  2. 编写mapper.xml文件;

    <!--RowBounds分页-->
    <select id="getUserByRowBounds" resultType="com.wang.pojo.User">
        select id,name,pwd as password from mybatis.user;
    </select>
  3. 测试。

    @Test
    public void testGetUserByRowBounds(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        RowBounds rowBounds = new RowBounds(1, 2);
        List<User> userByRowBounds = sqlSession.selectList("com.wang.dao.UserMapper.getUserByRowBounds", null, rowBounds);
        for (User user : userByRowBounds) {
            System.out.println(user);
        }
        sqlSession.close();
    }
    // 结果:
    User{id=2, name='Bob', password='abcdef'}
    User{id=3, name='Tony', password='987654'}

7.3 PageHelper插件

了解即可,官网:https://pagehelper.github.io/

8. 使用注解开发

8.1 面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程

  • 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好

  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;

  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

  • 接口的本身反映了系统设计人员对系统的抽象理解。

  • 接口应有两类:

    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);

    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);

  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .

  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .

  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构

8.2 使用注解开发

直接在接口上使用注解实现SQL语句,不需要编写mapper.xml,因此在核心配置文件中的mappers属性中不需要绑定配置文件,而需要绑定接口。本质上利用了jvm的动态代理机制。

  1. 在接口中添加注解;

    package com.wang.dao;
    
    import com.wang.pojo.User;
    import org.apache.ibatis.annotations.Select;
    
    import java.util.List;
    
    public interface UserMapper {
        @Select("select * from user")
        List<User> getUser();
    }
  2. 在mybatis的核心配置文件中注入;

    <!--绑定接口-->
    <mappers>
        <mapper class="com.wang.dao.UserMapper"/>
    </mappers>
  3. 测试。

    package com.wang.dao;
    
    import com.wang.pojo.User;
    import com.wang.utils.MybatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class UserDaoTest {
        @Test
        public void testGetUser(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = mapper.getUser();
            for (User user : userList) {
                System.out.println(user);
            }
            sqlSession.close();
        }
    }
    // 结果:
    User{id=1, name='Alice', pwd='123456'}
    User{id=2, name='Bob', pwd='abcdef'}
    User{id=3, name='Tony', pwd='987654'}
    User{id=4, name='Timi', pwd='123123'}

mybatis详细执行流程:

 

 

 

8.3 注解实现CRUD

  1. 编写接口;

    public interface UserMapper {
        @Select("select * from user")
        List<User> getUser();
    
        @Select("select * from user where id = #{uid}")
        User getUserById(@Param("uid") int id);
    
        @Insert("insert into user(id,name,pwd) values (#{id},#{name},#{pwd})")
        int insertUser(User user);
    
        @Update("update user set name=#{name},pwd=#{pwd} where id=#{id}")
        int updateUser(User user);
    
        @Delete("delete from user where id=#{uid}")
        int deleteUserById(@Param("uid") int id);
    }
  2. 测试。

    public class UserDaoTest {
        @Test
        public void testGetUser(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = mapper.getUser();
            for (User user : userList) {
                System.out.println(user);
            }
            sqlSession.close();
        }
    
        @Test
        public void testGetUserById(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User user = mapper.getUserById(1);
            System.out.println(user);
            sqlSession.close();
        }
    
        @Test
        public void testInsertUser(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User lily = new User(5, "Lily", "000000");
            mapper.insertUser(lily);
            sqlSession.close();
        }
    
        @Test
        public void testUpdateUser(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User sara= new User(5, "Sara", "000000");
            mapper.updateUser(sara);
            sqlSession.close();
        }
    
        @Test
        public void testDeleteUserById(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            mapper.deleteUserById(5);
            sqlSession.close();
        }
    }

注:

在MybatisUtils类中设置事务自动提交:

public static SqlSession getSqlSession(){
    // 参数true设置事务自动提交
    return sqlSessionFactory.openSession(true);
}

@Param注解:

  • 在方法只接受一个参数的情况下,可以不使用@Param;在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。

  • 如果参数是 JavaBean , 则不能使用@Param。

  • 不使用@Param注解时,参数只能有一个,并且最好是Javabean。

#{} 和 ${} 的区别:

  • #{} :主要是替换预编译语句(PrepareStatement)中的占位符;

  • ${} :直接进行字符串替换,会有sql注入的问题。

Lombok插件:

  • 用于快速生成构造器、getter、setter、toString、equals等函数。

  • 使用步骤:在IDEA中安装Lombok插件 --> 在项目中添加Maven依赖(导入lombok的jar包) --> 在程序上使用注解。

  • 例:

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private int id;
        private String name;
        private String password;
    }

9. 多对一和一对多处理

9.1 搭建测试环境

  1. 数据库设计;

    CREATE TABLE `teacher` (
      `id` INT(10) NOT NULL,
      `name` VARCHAR(30) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8
     
    INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');
     
    CREATE TABLE `student` (
      `id` INT(10) NOT NULL,
      `name` VARCHAR(30) DEFAULT NULL,
      `tid` INT(10) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `fktid` (`tid`),
      CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8
     
     
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
  2. 编写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>
        <properties resource="db.properties"/>
        
        <settings>
            <setting name="logImpl" value="STDOUT_LOGGING"/>
        </settings>
    
        <environments default="test">
            <environment id="test">
                <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>
        <mappers>
            <mapper class="com.wang.dao.StudentMapper"/>
            <mapper class="com.wang.dao.TeacherMapper"/>
        </mappers>
    </configuration>
  3. 编写mybatis工具类(略);

  4. 创建两个实体类;

    @Data
    public class Student {
        private int id;
        private String name;
        private Teacher teacher;    // 学生类关联老师类
    }
    @Data
    public class Teacher {
        private int id;
        private String name;
    }
  5. 编写实体类对应的Mapper接口 ;

    public interface StudentMapper {
    }
    public interface TeacherMapper {
    }
  6.  编写Mapper接口对应的 mapper.xml配置文件。

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.wang.dao.StudentMapper"></mapper>
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.wang.dao.TeacherMapper"></mapper>

     

9.2 多对一处理

  1. 给StudentMapper接口增加方法;

    public interface StudentMapper {
        //获取所有学生及对应老师的信息
        List<Student> getStudent1();
        List<Student> getStudent2();
    }
  2. 编写对应的Mapper文件(StudentMapper.xml);

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.wang.dao.StudentMapper">
    
        <!--方案一:按查询嵌套处理-->
        <select id="getStudent1" resultMap="StudentTeacher1">
            select * from mybatis.student;
        </select>
    
        <resultMap id="StudentTeacher1" type="com.wang.pojo.Student">
            <result property="id" column="id"/>
            <result property="name" column="name"/>
            <association property="teacher" column="tid" javaType="com.wang.pojo.Teacher" select="getTeacher"/>
        </resultMap>
    
        <select id="getTeacher" resultType="com.wang.pojo.Teacher">
            select * from mybatis.teacher where id=#{tid};
        </select>
    
        <!--方案二:按结果嵌套处理-->
        <select id="getStudent2" resultMap="StudentTeacher2">
            select s.id sid, s.name sname, t.id tid, t.name tname
            from mybatis.student s, mybatis.teacher t
            where s.tid = t.id
        </select>
    
        <resultMap id="StudentTeacher2" type="com.wang.pojo.Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <association property="teacher" javaType="com.wang.pojo.Teacher">
                <result property="id" column="tid"/>
                <result property="name" column="tname"/>
            </association>
        </resultMap>
    
    </mapper>
  3. 测试。

    public class UserDaoTest {
        @Test
        public void testGetStudents(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
            // List<Student> studentList = mapper.getStudent1();
            List<Student> studentList = mapper.getStudent2();
            for (Student student : studentList) {
                System.out.println(student);
            }
            sqlSession.close();
        }
    }
    // 结果:
    Student(id=1, name=JungKook, teacher=Teacher(id=1, name=Big Hit))
    Student(id=2, name=V, teacher=Teacher(id=1, name=Big Hit))
    Student(id=3, name=Jimin, teacher=Teacher(id=1, name=Big Hit))
    Student(id=4, name=Suga, teacher=Teacher(id=1, name=Big Hit))
    Student(id=5, name=Jim, teacher=Teacher(id=1, name=Big Hit))

9.3 一对多处理

环境搭建:除两个实体类有些许区别外,其余步骤可以参考9.1。两个实体类编写如下:

@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}
@Data
public class Teacher {
    private int id;
    private String name;
    private List<Student> students;
}
  1. 给TeachertMapper接口增加方法;

    public interface TeacherMapper {
        // 获取指定ID老师下的所有学生及老师信息
        Teacher getTeacher1(@Param("tid") int id);
        Teacher getTeacher2(@Param("tid") int id);
    }
  2. 编写对应的Mapper文件(TeacherMapper.xml);

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.wang.dao.TeacherMapper">
    
        <!--方案一:按照查询嵌套处理-->
        <select id="getTeacher1" resultMap="TeacherStudent1">
            select * from mybatis.teacher where id=#{tid};
        </select>
    
        <resultMap id="TeacherStudent1" type="com.wang.pojo.Teacher">
            <collection property="students" column="id" javaType="ArrayList" ofType="com.wang.pojo.Student" select="getStudent"/>
        </resultMap>
    
        <select id="getStudent" resultType="com.wang.pojo.Student">
            select * from mybatis.student where tid=#{tid};
        </select>
    
        <!--方案二:按照结果嵌套处理-->
        <select id="getTeacher2" resultMap="TeacherStudent2">
            select s.id sid, s.name sname, t.id tid, t.name tname
            from mybatis.student s, mybatis.teacher t
            where s.tid = t.id and t.id = #{tid}
        </select>
    
        <resultMap id="TeacherStudent2" type="com.wang.pojo.Teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tname"/>
            <collection property="students" ofType="com.wang.pojo.Student">
                <result property="id" column="sid"/>
                <result property="name" column="sname"/>
                <result property="tid" column="tid"/>
            </collection>
        </resultMap>
    
    </mapper>
  3. 测试。

    public class UserDaoTest {
        @Test
        public void testGetStudents(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
            // Teacher teacher = mapper.getTeacher1(1);
            Teacher teacher = mapper.getTeacher2(1);
            System.out.println(teacher);
            sqlSession.close();
        }
    }
    // 结果:
    Teacher(id=1, name=Big Hit, students=[Student(id=1, name=JungKook, tid=1), Student(id=2, name=V, tid=1), Student(id=3, name=Jimin, tid=1), Student(id=4, name=Suga, tid=1), Student(id=5, name=Jim, tid=1)])

9.4 小结

  • association用于一对一和多对一;collection是用于一对多的关系。

  • JavaType和ofType都是用来指定对象类型的

    • JavaType是用来指定pojo中属性的类型

    • ofType指定的是映射到list集合属性中pojo的类型。

10. 动态SQL

动态SQL指的是根据不同的查询条件,生成不同的Sql语句。

  • if

  • choose (when, otherwise)

  • trim (where, set)

  • foreach

10.1 搭建环境

  1. 在数据库mybatis中新建表blog;

    CREATE TABLE `blog` (
      `id` varchar(50) NOT NULL COMMENT '博客id',
      `title` varchar(100) NOT NULL COMMENT '博客标题',
      `author` varchar(30) NOT NULL COMMENT '博客作者',
      `create_time` datetime NOT NULL COMMENT '创建时间',
      `views` int(30) NOT NULL COMMENT '浏览量'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
  2. 创建子项目,导入lombok依赖以减少实体类代码量;

  3. 编写MyBatisUtils类(参考之前的)和IDUtil工具类;

    public class IdUtils {
        public static String GetId(){
            return UUID.randomUUID().toString().replaceAll("-", "");
        }
    }
  4. 编写Blog实体类;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Blog {
        private String id;
        private String title;
        private String author;
        private Date createTime;
        private int views;
    }
  5. 编写Mapper接口及xml文件;

    public interface BlogMapper {
    }
    <?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.wang.dao.BlogMapper">
    
    </mapper>
  6. 编写核心配置文件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>
        <properties resource="db.properties"/>
        
        <settings>
            <setting name="logImpl" value="STDOUT_LOGGING"/>
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
        
        <typeAliases>
            <package name="com.wang.pojo"/>
        </typeAliases>
    
        <environments default="test">
            <environment id="test">
                <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>
    
        <mappers>
            <mapper class="com.wang.dao.BlogMapper"/>
        </mappers>
    </configuration>
  7. 向blog表中插入初始数据。

    • 给BlogMapper接口增加方法;

      public interface BlogMapper {
          // 添加博客
          int addBlog(Blog blog);
      }
    • 编写对应的BlogMapper.xml文件;

      <insert id="addBlog" parameterType="blog">
          insert into mybatis.blog(id, title, author, create_time, views)
          values (#{id}, #{title}, #{author}, #{createTime}, #{views});
      </insert>
    • 编写初始化博客方法。

      @Test
      public void testAddBlog(){
          SqlSession sqlSession = MybatisUtils.getSqlSession();
          BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
      
          Blog blog = new Blog();
          blog.setId(IdUtils.GetId());
          blog.setTitle("Java基础");
          blog.setAuthor("Kyla");
          blog.setCreateTime(new Date());
          blog.setViews(10000);
          mapper.addBlog(blog);
      
          blog.setId(IdUtils.GetId());
          blog.setTitle("JavaWeb");
          blog.setCreateTime(new Date());
          blog.setViews(10000);
          mapper.addBlog(blog);
      
          blog.setId(IdUtils.GetId());
          blog.setTitle("MySQL");
          blog.setAuthor("Tony");
          blog.setCreateTime(new Date());
          blog.setViews(5000);
          mapper.addBlog(blog);
      
          blog.setId(IdUtils.GetId());
          blog.setTitle("MyBatis");
          blog.setCreateTime(new Date());
          blog.setViews(5000);
          mapper.addBlog(blog);
          sqlSession.close();
      }

10.2 语句案例

例1:

<select id="queryBlog" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <if test="author != null">
            and author = #{author}
        </if>
        <if test="views != null">
            and views = #{views}
        </if>
    </where>
</select>

例2:

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                and author = #{author}
            </when>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

例3:其中map中的ids为一个ArrayList,包含了要查询的博客ID

<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
            id = #{id}
        </foreach>
    </where>
</select>

例4:

<update id="updateBlog" parameterType="map">
    update mybatis.blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author}
            </if>
        </set>
    where id = #{id};
</update>

注:

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

10.3 SQL片段

<!--sql片段-->
<sql id="ifBlock">
    <if test="author != null">
        and author = #{author}
    </if>
    <if test="views != null">
        and views = #{views}
    </if>
</sql>
<!--引用sql片段-->
<select id="queryBlog" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <include refid="ifBlock"></include>
    </where>
</select>

11.缓存

  • 什么是缓存?

    • 存在内存中的临时数据。

    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

  • 为什么使用缓存?减少和数据库的交互次数,减少系统开销,提高系统效率。

  • 什么样的数据能使用缓存?经常查询并且不经常改变的数据。

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

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

    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)

    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

    • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。

  • 寻找查询对象顺序:二级缓存(namespace) --> 一级缓存(sqlSession) --> 查询数据库。

注:只有查询才有缓存,根据数据是否需要缓存(修改是否频繁选择是否开启)useCache=“true”。

<select id="getUserById" resultType="user" useCache="true">
    select * from user where id = #{id}
</select>

11.1 一级缓存

一级缓存也叫本地缓存:

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中;

  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库。

测试:在同一次会话中查询id为1的用户时,SQL语句只查询了一次,第二次结果的获取没有查询数据库,两次返回的是同一个对象。

@Test
public void testQueryById(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user1 = mapper.queryUserById(1);
    System.out.println(user1);
    System.out.println("===============================");
    User user2 = mapper.queryUserById(1);
    System.out.println(user2);
    System.out.println(user1 == user2);
    sqlSession.close();
}

 

 

 

一级缓存失效的四种情况:

  • sqlSession不同,每个sqlSession中的缓存相互独立。如上述测试中使用两个sqlSession查询同一ID,SQL查询语句会执行了两次。

  • sqlSession相同,查询条件不同。如上述测试中使用同一个sqlSession查询两个ID不同的User。

  • sqlSession相同,两次查询之间执行了增删改操作。增删改操作,可能会改变原来的数据,所以必定会刷新缓存。

  • sqlSession相同,手动清除一级缓存(session.clearCache();)。

11.2 二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;

    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;

    • 新的会话查询信息,就可以从二级缓存中获取内容;

    • 不同的mapper查出的数据会放在自己对应的缓存(map)中。

步骤:

  1. 在核心配置文件中开去全局缓存;

    <setting name="cacheEnabled" value="true"/>
  2. 去每个mapper.xml中配置使用二级缓存;

    <!--方法一-->
    <cache/>
    
    <!--方法二:修改属性-->
    <cache
        eviction="FIFO"
        flushInterval="60000"
        size="512"
        readOnly="true"/>
  3. 测试(所有的实体类先实现序列化接口):在两个不同会话中查询id为1的用户时,SQL语句只查询了一次,第二次结果的获取没有查询数据库,因为序列化是深拷贝,所以反序列化后的对象和原来的对象不是同一个对象,因而返回false。

    @Test
    public void testQueryUserById(){
        SqlSession session = MybatisUtils.getSqlSession();
        SqlSession session2 = MybatisUtils.getSqlSession();
        UserMapper mapper1 = session.getMapper(UserMapper.class);
        UserMapper mapper2 = session2.getMapper(UserMapper.class);
        User user = mapper1.queryUserById(1);
        System.out.println(user);
        session.close();
    
        User user2 = mapper2.queryUserById(1);
        System.out.println(user2);
        System.out.println(user==user2);
        session2.close();
    }

  •  

     

     只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据;

  • 查出的数据都会被默认先放在一级缓存中;

  • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中。

附:狂神b站视频链接

posted @ 2021-04-03 13:36  甜了酒了果  阅读(83)  评论(0编辑  收藏  举报