Mybatis

视频:2、第一个Mybatis程序_哔哩哔哩_bilibili

文档:狂神SSM教程- 专栏 -KuangStudy

一.什么是Mybatis 

1.什么是MyBatis

  • MyBatis 是一款优秀的持久层框架
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程
  • MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 实体类 【Plain Old Java Objects,普通的 Java对象】映射成数据库中的记录。
  • MyBatis 本是apache的一个开源项目ibatis, 2010年这个项目由apache 迁移到了google code,并且改名为MyBatis 。
  • 2013年11月迁移到Github .
  • Mybatis官方文档 : http://www.mybatis.org/mybatis-3/zh/index.html
  • GitHub : GitHub - mybatis/mybatis-3: MyBatis SQL mapper framework for Java

2.持久化

  • 持久化是将程序数据在持久状态和瞬时状态间转换的机制。
    • 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。
    • JDBC就是一种持久化机制。文件IO也是一种持久化机制。
    • 在生活中 : 将鲜肉冷藏,吃的时候再解冻的方法也是。将水果做成罐头的方法也是。
  • 为什么需要持久化服务呢?那是由于内存本身的缺陷引起的
    • 内存断电后数据会丢失,但有一些对象是无论如何都不能丢失的,比如银行账号等,遗憾的是,人们还无法保证内存永不掉电。
    • 内存过于昂贵,与硬盘、光盘等外存相比,内存的价格要高2~3个数量级,而且维持成本也高,至少需要一直供电吧。所以即使对象不需要永久保存,也会因为内存的容量限制不能一直呆在内存中,需要持久化来缓存到外存。

3.持久层

  • 什么是持久层?
    • 完成持久化工作的代码块 . ——> dao层 【DAO (Data Access Object) 数据访问对象】
    • 大多数情况下特别是企业级应用,数据持久化往往也就意味着将内存中的数据保存到磁盘上加以固化,而持久化的实现过程则大多通过各种关系数据库来完成。
    • 不过这里有一个字需要特别强调,也就是所谓的“层”。对于应用系统而言,数据持久功能大多是必不可少的组成部分。也就是说,我们的系统中,已经天然的具备了“持久层”概念?也许是,但也许实际情况并非如此。之所以要独立出一个“持久层”的概念,而不是“持久模块”,“持久单元”,也就意味着,我们的系统架构中,应该有一个相对独立的逻辑层面,专著于数据持久化逻辑的实现.
    • 与系统其他部分相对而言,这个层面应该具有一个较为清晰和严格的逻辑边界。 【说白了就是用来操作数据库存在的!】

4.为什么需要Mybatis

  • Mybatis就是帮助程序猿将数据存入数据库中 , 和从数据库中取数据 .
  • 传统的jdbc操作 , 有很多重复代码块 .比如 : 数据取出时的封装 , 数据库的建立连接等等… , 通过框架可以减少重复代码,提高开发效率 .
  • MyBatis 是一个半自动化的ORM框架 (Object Relationship Mapping) —>对象关系映射
  • 所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单!技术没有高低之分,只有使用这个技术的人有高低之别

  • MyBatis的优点

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

二.第一个Mybatis程序 

思路流程:搭建环境—>导入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,'狂神','123456'),(2,'张三','abcdef'),(3,'李四','987654');

2.导入Mybatis 

 Maven Repository: org.mybatis » mybatis » 3.5.6 (mvnrepository.com)

Maven Repository: mysql » mysql-connector-java (mvnrepository.com)

Maven Repository: junit » junit (mvnrepository.com)

 Maven Repository: org.projectlombok » lombok (mvnrepository.com)

 3.编写代码

创建一个普通的maven项目

配置pom.xml

  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>

在resource下编写MyBatis核心配置文件mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
    
    <environments default="development">
        <environment id="development">
            <!--事务作用域和控制方式的事务管理器(TransactionManager)-->
            <transactionManager type="JDBC"/>
            <!--数据库连接实例的数据源(DataSource)-->
            <dataSource type="POOLED">
            <!--配置driver,url,username,password-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    
</configuration>

编写编写MyBatis工具类读取配置

package com.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 {
            // 从 XML 文件中构建 SqlSessionFactory 的实例
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //获取SqlSession连接
    public static SqlSession getSession(){
        return sqlSessionFactory.openSession();
    }
}

 创建实体类

package com.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;  //id
    private String name;   //姓名
    private String pwd;   //密码

}

编写Mapper接口类

package com.dao;

import com.pojo.User;
import java.util.List;

public interface UserMapper {
    List<User> selectUser();
}

编写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">
        <!--namespace绑定一个Dao/Mapper接口-->
<mapper namespace="com.dao.UserMapper">
        <!--select查询语句 id绑定接口方法 resultType是返回值类型,resultMap是返回值集合-->
    <select id="selectUser" resultType="com.pojo.User">
        select * from user
    </select>
</mapper>

去MyBatis核心配置文件注册Mapper.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>

    <environments default="development">
        <environment id="development">
            <!--事务作用域和控制方式的事务管理器(TransactionManager)-->
            <transactionManager type="JDBC"/>
            <!--数据库连接实例的数据源(DataSource)-->
            <dataSource type="POOLED">
            <!--配置driver,url,username,password-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!--注册Mapper.xml配置文件-->
    <mappers>
        <mapper resource="com/dao/Mapper.xml"/>
    </mappers>

</configuration>

编写测试类

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 UserMapperTest {
    @Test
    public void selectUser(){
        // 获取SqlSession
        SqlSession session = MybatisUtils.getSession();

        // getMapper()
        // List<User> users = session.selectList("com.dao.mapper.UserMapper.selectUser");老方式
        UserMapper userMapper = session.getMapper(UserMapper.class);
        List<User> users = userMapper.selectUser();

        for (User user: users) {
            System.out.println(user);
        }

        // 关闭SqlSession
        session.close();

    }
}

 改善

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 UserMapperTest {
    @Test
    public void selectUser(){
        // 获取SqlSession
        SqlSession session = MybatisUtils.getSession();

        try {
            // getMapper()
            // List<User> users = session.selectList("com.dao.mapper.UserMapper.selectUser");老方式
            UserMapper userMapper = session.getMapper(UserMapper.class);
            List<User> users = userMapper.selectUser();

            for (User user: users) {
                System.out.println(user);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 关闭SqlSession
            session.close();
        }

    }
}

4.错误 

1. java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing

导入以下结包Maven Repository: org.hamcrest » hamcrest » 2.2 (mvnrepository.com) 

2.org.apache.ibatis.binding.BindingException: Type interface com.Dao.UserMapper is not known to the MapperRegistry.

去MyBatis核心配置文件注册Mapper.xml配置文件

三.CRUD操作 

 1.查找

(1).id查询 

package com.dao;

import com.pojo.User;
import java.util.List;
public interface UserMapper {
    //查询全部用户
    List<User> selectUser();
    //根据id查询用户
    User selectUserById(int id);
}
<?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.UserMapper">
        <!--select查询语句 id绑定接口方法 resultType是返回值类型,resultMap是返回值集合-->
    <select id="selectUser" resultType="com.pojo.User">
        select * from user
    </select>

        <!--parameterType参数类型-->
    <select id="selectUserById" parameterType="int" resultType="com.pojo.User">            
        <!--#{id}获取id-->
        select * from user where id = #{id}
    </select>
</mapper>
    @Test
    public void selectUserById() {
        SqlSession session = MybatisUtils.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.selectUserById(1);

        try {
            System.out.println(user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            session.close();
        }
    }

(2).模糊查询,两种方式

第1种:在Java代码中添加sql通配符。

   SqlSession session = MybatisUtils.getSession();

   UserMapper userMapper = session.getMapper(UserMapper.class);
   User user = userMapper.selectUserName("%李%);

第2种:在sql语句中拼接通配符,会引起sql注入 

        <!--parameterType参数类型-->
    <select id="selectUserById" parameterType="String" resultType="com.pojo.User">

        select * from user where name like "%"#{name}"%";
    </select>

2. 增删改

 

package com.dao;

import com.pojo.User;
import java.util.List;

public interface UserMapper {
    //查询全部用户
    List<User> selectUser();
    //根据id查询用户
    User selectUserById(int id);

    // 添加用户
    int addUser(User user);

    // 删除用户
    int deleteUser(int id);

    // 更改用户
    int updateUser(User user);
}

 Mapper.xml

<!--添加返回值类型可以不写-->
    <insert id="addUser" parameterType="com.pojo.User" >
--         对象中的属性可以直接取出
        insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
    </insert>
    
    <delete id="deleteUser" parameterType="int">
        delete from user where id = #{id}
    </delete>

    <update id="updateUser" parameterType="com.pojo.User">
        update user set pwd = #{pwd} where id = #{id}
    </update>
    @Test
    public void addUser() {
        SqlSession session = MybatisUtils.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);
        int add = userMapper.addUser(new User(4,"lisa","452423423"));

        if(add > 0) {
            System.out.println("添加成功");
        }
        // 增删改需要提交事务
        session.commit();

        session.close();
    }

    @Test
    public void deleteUser() {
        SqlSession session = MybatisUtils.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);
        int delete = userMapper.deleteUser(1);

        if(delete > 0) {
            System.out.println("删除成功");
        }
        session.commit();

        session.close();
    }

    @Test
    public void updateUser() {
        SqlSession session = MybatisUtils.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);

        int update = userMapper.updateUser(new User(2,"张三","111111"));

        if(update > 0) {
            System.out.println("修改成功");
        }
        session.commit();

        session.close();
    }

3.使用万能的Map 

如果参数过多,我们可以考虑直接使用Map实现,如果参数比较少,直接传递参数即可 

接口UserMapper

    // 添加用户
    int addUser(User user);

    // 使用Map为参数,添加用户
    int addUser2(Map<String,Object> map);

 Mapper.xml

         <!--添加返回值类型可以不写-->
    <insert id="addUser" parameterType="com.pojo.User" >
        <!--对象中的属性可以直接取出-->
        insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
    </insert>

    <insert id="addUser2" parameterType="com.pojo.User" >
           <!--这里的#{}对应的Map的键,使用可以随意写,不用对应实体类与数据库字段-->
        insert into user (id,name,pwd) values (#{helloId},#{manehello},#{p})
    </insert>

测试

  @Test
    public void addUser() {
        SqlSession session = MybatisUtils.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);
        int add = userMapper.addUser(new User(4,"lisa","452423423"));

        if(add > 0) {
            System.out.println("添加成功");
        }
        // 增删改需要提交事务
        session.commit();

        session.close();
    }

    @Test
    public void addUser2() {
        SqlSession session = MybatisUtils.getSession();

        try {
            UserMapper userMapper = session.getMapper(UserMapper.class);

            Map<String,Object> map = new HashMap<String, Object>();

            // 对应Mapping配置的key,放入数据库字段对应的值
            map.put("helloId",1);
            map.put("manehello","you");
            map.put("p","555555");

            userMapper.addUser2(map);

            // 增删改需要提交事务
            session.commit();

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            session.close();
        }

    }

四.MyBatis配置解析 

 1.解析核心配置文件mybatis-config.xml

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

注意元素节点的顺序!顺序不对会报错 

2.environments,properties

db.properties 

driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
username = root
password = 123456

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>

    <!-- 引入配置文件,也可以使用property标签在properties标签中配置,优先顺序外先内后-->
<properties resource="db.properties">
    <property name="password" value="123456"/>
</properties>

    <!--
    environments:环境配置
    default:默认环境变量
    这里默认的环境变量是development,也可以选择下面的text
     -->
    <environments default="development">
        <!--
        environment:环境变量
        id:此环境变量的id是什么,这里的id是development
        -->
        <environment id="development">
            <!--
            TransactionManager:事务作用域和控制方式的事务管理器
            type:类型,总共有两种type="[JDBC|MANAGED]"
            1.JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
            2.MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。
              默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
             -->
            <transactionManager type="JDBC"/>
            <!--
            DataSource:数据库连接实例的数据源
            type:类型,总共有三种 type="[UNPOOLED|POOLED|JNDI]"
            1.UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接
            2.POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
              这种处理方式很流行,能使并发 Web 应用快速响应请求。
            3.JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
            -->
            <dataSource type="POOLED">

            <!--配置driver,url,username,password-->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>

        </environment>

        <environment id="text">
            <!--事务作用域和控制方式的事务管理器(TransactionManager)-->
            <transactionManager type="JDBC"/>
            <!--数据库连接实例的数据源(DataSource)-->
            <dataSource type="POOLED">
                <!--配置driver,url,username,password-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

</configuration>

3.typeAliases(别名)

下面是刚才在UserMapper.xml配置的sql语句,其中resultType的返回值类型非常长,我们可以通过别名把它减短

<select id="selectUser" resultType="com.pojo.User">
        select * from user
</select>

在mybatis-config.xml中配置

方式一:

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

方式二:

  <typeAliases>
        <!--默认为包名小写,如果要自定义可以在具体的实体类中加注解-->
        <package name="com.pojo"/>
   </typeAliases>
@Alias("user")
public class User {
    ...
}

 简化后的UserMapper.xml配置的sql语句

     <select id="selectUser" resultType="user">
        select * from user
    </select>

下面是一些为常见的 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.settings(设置)

<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>

只要记住下面三个 

cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置

5. mappers(映射器)

 

6.其它 

7.生命周期和作用域

理解我们目前已经讨论过的不同作用域和生命周期类是至关重要的,因为错误的使用会导致非常严重的并发问题 

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            // 从 XML 文件中构建 SqlSessionFactory 的实例
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //获取SqlSession连接
    public static SqlSession getSession(){
        return sqlSessionFactory.openSession();
    }
}

 

五.ResultMap要解决的问题:属性名和字段名不一致 

@Data
@NoArgsConstructor
@AllArgsConstructor
@Alias("user")

public class User {
    private int id;  //id
    private String name;   //姓名
    // 密码 private String pwd;
    // 字段不一致
    private String passWord;
}

按刚才的步骤查询用户运行发现密码为null 

解决:

方式一:为列名指定别名 , 别名和java实体类的属性名一致

<select id="selectUserById" resultType="User">
    select id , name , pwd as password from user where id = #{id}
</select>

方式二:使用结果集映射->ResultMap 【推荐】

<resultMap id="UserMap" type="User">
    <!-- id为主键 -->
    <id column="id" property="id"/>
    <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
</resultMap>

<select id="selectUserById" resultMap="UserMap">
    select id , name , pwd from user where id = #{id}
</select>

 六.MyBatis分页的实现

1.日志工厂 

思考:我们在测试SQL的时候,要是能够在控制台输出 SQL 的话,是不是就能够有更快的排错效率?

曾经:sout,debug

现在:日志工厂

logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置

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

  • SLF4J 
  • LOG4J 【需要掌握】
  • LOG4J2
  • JDK_LOGGING 
  •  COMMONS_LOGGING
  •  STDOUT_LOGGING 【需要掌握】
  • NO_LOGGING

具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。 如果一个都未找到,日志功能就会被禁用。 

标准日志实现,在核心配置文件中配置

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

2.Log4j 

简介:

  • Log4j是Apache的一个开源项目
  • 通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件….
  • 我们也可以控制每一条日志的输出格式;
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

使用步骤:

  • 导入log4j的包Maven Repository: log4j » log4j (mvnrepository.com)

  • 配置文件编写:在resources文件夹下创建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/kuang.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

  • setting设置日志实现,在核心配置文件中配置
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>

  • 在程序中使用Log4j进行输出!
    public class UserMapperTest {
    
        // 注意导包:org.apache.log4j.Logger
        static Logger logger = Logger.getLogger(UserMapperTest.class);
    
        @Test
        public void selectUser(){
    
            // 日志等级
            logger.info("信息 info:进入selectUser方法");
            logger.debug("调试 debug:进入selectUser方法");
            logger.error("严重错误 error: 进入selectUser方法");
    
    
            // 获取SqlSession
            SqlSession session = MybatisUtils.getSession();
    
            try {
                // getMapper()
                // List<User> users = session.selectList("com.dao.mapper.UserMapper.selectUser");老方式
                UserMapper userMapper = session.getMapper(UserMapper.class);
                List<User> users = userMapper.selectUser();
    
                for (User user: users) {
                    System.out.println(user);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                // 关闭SqlSession
                session.close();
            }
        }
    }

 3.limit实现分页

思考:为什么需要分页?

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

使用Limit实现分页 

public interface UserMapper {

    // 选择全部用户实现分页
    List<User> selectUser(Map<String,Object> map);

}
    <!-- 注意Mapper.xml配置文件中一个id对应着一个方法,方法的重载会报错-->
    <select id="selectUser" parameterType="map" resultType="user">
        select * from user limit #{startIndex},#{pageSize}
    </select>
    @Test
    public void selectLimitUser() {
        SqlSession session = MybatisUtils.getSession();

        try {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            Map<String,Object> map = new HashMap<String, Object>();
            map.put("startIndex",0);
            map.put("pageSize",2);
            List<User> users = userMapper.selectUser(map);
            for (User user: users) {
                System.out.println(user);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            session.close();
        }
    }

4.RowBounds分页 

我们除了使用Limit在SQL层面实现分页,也可以使用RowBounds在Java代码层面实现分页,当然此种方式作为了解即可。我们来看下如何实现的!

public interface UserMapper {

    // 选择全部用户RowBounds实现分页
    List<User> getUserByRowBounds();

}
     <select id="getUserByRowBounds" resultType="user">
        select * from user
    </select>
    @Test
    public void getUserByRowBounds() {
        SqlSession session = MybatisUtils.getSession();

        try {
            int currentPage = 2;  //第几页
            int pageSize = 2;  //每页显示几个
            RowBounds rowBounds = new RowBounds((currentPage-1)*2,pageSize);
            // 通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
            List<User> users = session.selectList("com.dao.UserMapper.getUserByRowBounds",null,rowBounds);
            for (User user: users) {
                System.out.println(user);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            session.close();
        }
    }

 5.PageHelper分页

MyBatis 分页插件 PageHelper

 七.使用注解开发

1.增删改查 

public interface UserMapper {
    // 查询全部用户
    @Select("select * from user")
    List<User> selectUser();

    //根据id查询用户
    @Select("select * from user where id = #{id}")
    // @Param注解用于给方法参数起一个名字。以下是总结的使用原则:
    // 在方法只接受一个参数的情况下,可以不使用@Param。
    // 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
    // 如果参数是 JavaBean , 则不能使用@Param。
    // 不使用@Param注解时,参数只能有一个,并且是Javabean。
    User selectUserById(@Param("id") int id);

    // 添加用户
    @Insert("insert into user (id,name,pwd) values (#{id},#{name},#{pwd})")
    int addUser(User user);

    // 删除用户
    @Delete("delete from user where id = #{id}")
    int deleteUser(@Param("id") int id);

    // 更改用户
    @Update("update user set name = #{name} where id = {id}")
    int updateUser(User user);

}

在核心配置文件中注册

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

 测试的代码是不变的

    // 注意导包:org.apache.log4j.Logger
    static Logger logger = Logger.getLogger(UserMapperTest.class);    

    @Test
    public void selectUser(){

        // 日志等级
        logger.info("信息 info:进入selectUser方法");
        logger.debug("调试 debug:进入selectUser方法");
        logger.error("严重错误 error: 进入selectUser方法");


        // 获取SqlSession
        SqlSession session = MybatisUtils.getSession();

        try {
            // getMapper()
            // List<User> users = session.selectList("com.dao.mapper.UserMapper.selectUser");老方式
            UserMapper userMapper = session.getMapper(UserMapper.class);
            List<User> users = userMapper.selectUser();

            for (User user: users) {
                System.out.println(user);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 关闭SqlSession
            session.close();
        }

    }

    @Test
    public void selectUserById() {
        SqlSession session = MybatisUtils.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.selectUserById(1);

        try {
            System.out.println(user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            session.close();
        }
    }

    @Test
    public void addUser() {
        SqlSession session = MybatisUtils.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);
        int add = userMapper.addUser(new User(4,"lisa","452423423"));

        if(add > 0) {
            System.out.println("添加成功");
        }
        // 增删改需要提交事务
        session.commit();

        session.close();
    }



    @Test
    public void deleteUser() {
        SqlSession session = MybatisUtils.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);
        int delete = userMapper.deleteUser(1);

        if(delete > 0) {
            System.out.println("删除成功");
        }
        session.commit();

        session.close();
    }

    @Test
    public void updateUser() {
        SqlSession session = MybatisUtils.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);

        int update = userMapper.updateUser(new User(2,"张三","111111"));

        if(update > 0) {
            System.out.println("修改成功");
        }
        session.commit();

        session.close();
    }

2.本质 

利用Debug查看本质

本质上利用了jvm的动态代理机制

3.Mybatis详细的执行流程

 

4.#与$的区别 

{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】

INSERT INTO user (name) VALUES (#{name});
INSERT INTO user (name) VALUES (?);

${} 的作用是直接进行字符串替换 

INSERT INTO user (name) VALUES ('${name}');
INSERT INTO user (name) VALUES ('kuangshen');

八.多对一的处理

 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');

实体类 

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
    private int id;
    private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

编写实体类对应的Mapper接口 【两个】 

public interface TeacherMapper {
}
public interface StudentMapper {
}

编写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.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.dao.TeacherMapper">

</mapper>

2.按查询嵌套处理(像子查询)

给StudentMapper接口增加方法 

//获取所有学生及对应老师的信息
public List<Student> getStudents();

编写对应的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.dao.StudentMapper">

    <select id="getStudents" resultMap="StudentTeacher">
        select * from student
    </select>

    <resultMap id="StudentTeacher" type="Student">
        <!--association – 一个复杂类型的关联;使用它来处理关联查询
        association关联属性  property属性名 javaType属性类型 column在多的一方的表中的列名-->
        <association property="teacher"  column="tid" javaType="Teacher" select="getTeacher"/>
    </resultMap>

    <!--
    这里传递过来的id,只有一个属性的时候,下面可以写任何值
    association中column多参数配置:
        column="{key=value,key=value}"
        其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
    -->
    <select id="getTeacher" resultType="teacher">
        select * from teacher where id = #{id}
    </select>

</mapper>

编写完毕去Mybatis配置文件中,注册Mapper!

    <mappers>
        <mapper resource="com/dao/StudentMapper.xml"/>
    </mappers>

 测试

    @Test
    public void getStudents() {
        SqlSession session = MybatisUtils.getSession();

        StudentMapper mapper = session.getMapper(StudentMapper.class);
        List<Student> students = mapper.getStudents();

        for (Student student : students) {
            System.out.println(student);
        }
         session.close();
    }

3.按结果嵌套处理(像联表查询)

接口方法编写

public List<Student> getStudents2();

编写对应的mapper文件

<!--
按查询结果嵌套处理
思路:
    1. 直接查询出结果,进行结果集的映射
-->
<select id="getStudents2" resultMap="StudentTeacher2" >
    select s.id sid, s.name sname , t.name tname
    from student s,teacher t
    where s.tid = t.id
</select>

<resultMap id="StudentTeacher2" type="Student">
    <id property="id" column="sid"/>
    <result property="name" column="sname"/>
    <!--关联对象property 关联对象在Student实体类中的属性-->
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>

去mybatis-config文件中注入【此处应该处理过了】

测试

    @Test
    public void getStudents2() {
        SqlSession session = MybatisUtils.getSession();

        StudentMapper mapper = session.getMapper(StudentMapper.class);
        List<Student> students = mapper.getStudents();

        for (Student student : students) {
            System.out.println(student);
        }
         session.close();
    }

九.一对多的处理

  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');

实体类 

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
    private int id;
    private String name;
    // 老师有多个学生
    private List<Student> students;
}
​
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;
    private int tid;
}

​

编写实体类对应的Mapper接口 【两个】 

public interface TeacherMapper {
}
public interface StudentMapper {
}

编写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.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.dao.TeacherMapper">

</mapper>

2.按查询嵌套处理(像子查询) 

TeacherMapper接口编写方法

public Teacher getTeacher(int id);

编写接口对应的Mapper配置文件

   <select id="getTeacher" resultMap="TeacherStudent">
        select * from teacher where id = #{id}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <!--column是一对多的外键 , 写的是一的主键的列名 -->
        <collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
    </resultMap>
    <select id="getStudentByTeacherId" resultType="Student">
        select * from student where tid = #{id}
    </select>

将Mapper文件注册到MyBatis-config文件中

   <mappers>
        <mapper resource="com/dao/TeacherMapper.xml"/>
    </mappers>

 测试

    @Test
    public void getTeacher() {
        SqlSession session = MybatisUtils.getSession();

        TeacherMapper mapper = session.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacher(1);

        System.out.println(teacher);
// 结果       Teacher(id=0, name=秦老师, 
//                students=[Student(id=1, name=小明, tid=1), 
//                Student(id=2, name=小红, tid=1), 
//                Student(id=3, name=小张, tid=1), 
//                Student(id=4, name=小李, tid=1), 
//                Student(id=5, name=小王, tid=1)])
        
        session.close();
}

3.按结果嵌套处理(像联表查询)

TeacherMapper接口编写方法

//获取指定老师,及老师下的所有学生
public Teacher getTeacher2(int id);

 编写接口对应的Mapper配置文件

        <select id="getTeacher2" resultMap="TeacherStudent2">
            select s.id sid, s.name sname , t.name tname, t.id tid
            from student s,teacher t
            where s.tid = t.id and t.id=#{id}
        </select>
        <resultMap id="TeacherStudent2" type="Teacher">
            <result  property="name" column="tname"/>
            <collection property="students" ofType="Student">
                <result property="id" column="sid" />
                <result property="name" column="sname" />
                <result property="tid" column="tid" />
            </collection>
        </resultMap>

将Mapper文件注册到MyBatis-config文件中

测试 

    @Test
    public void getTeacher2() {
        SqlSession session = MybatisUtils.getSession();

        TeacherMapper mapper = session.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacher(1);

        System.out.println(teacher);
// 结果       Teacher(id=0, name=秦老师,
//                students=[Student(id=1, name=小明, tid=1),
//                Student(id=2, name=小红, tid=1),
//                Student(id=3, name=小张, tid=1),
//                Student(id=4, name=小李, tid=1),
//                Student(id=5, name=小王, tid=1)])

        session.close();

    }

十.动态SQL 

 1.简介

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


2.搭建环境 

 新建一个数据库表: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

实体类编写 【注意set方法作用】 

@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime; // 这里与数据库不对应,后面会在核心配置文件中配置
    private int views;
}

IDutil工具类(随机数)

package com.utils;

import java.util.UUID;

public class IDUtil {
    public static String genId(){
        return UUID.randomUUID().toString().replace("-","");
    }
}

编写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.dao.BlogMapper">
</mapper>

 mybatis核心配置文件,下划线驼峰自动转换

mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true | false False

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <mappers>
        <mapper resource="com/dao/BlogMapper.xml"/>
    </mappers>

插入初始数据 

编写接口

//新增一个博客
int addBlog(Blog blog);

sql配置文件

<insert id="addBlog" parameterType="blog">
    insert into blog (id, title, author, create_time, views)
    values (#{id},#{title},#{author},#{createTime},#{views});
</insert>

 初始化博客方法

@Test
public void addInitBlog(){
    SqlSession session = MybatisUtils.getSession();
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    Blog blog = new Blog();
    blog.setId(IDUtil.genId());
    blog.setTitle("Mybatis如此简单");
    blog.setAuthor("狂神说");
    blog.setCreateTime(new Date());
    blog.setViews(9999);
    mapper.addBlog(blog);
    blog.setId(IDUtil.genId());
    blog.setTitle("Java如此简单");
    mapper.addBlog(blog);
    blog.setId(IDUtil.genId());
    blog.setTitle("Spring如此简单");
    mapper.addBlog(blog);
    blog.setId(IDUtil.genId());
    blog.setTitle("微服务如此简单");
    mapper.addBlog(blog);
    session.close();
}

 初始化数据完毕!

3.if 语句

需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询

编写接口类

//需求1
List<Blog> queryBlogIf(Map map);

编写SQL语句

<!--需求1:
根据作者名字和博客名字来查询博客!
如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
select * from blog where title = #{title} and author = #{author}
-->
<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog where
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>

 测试

@Test
public void testQueryBlogIf(){
    SqlSession session = MybatisUtils.getSession();
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    HashMap<String, String> map = new HashMap<String, String>();
    map.put("title","Mybatis如此简单");
    map.put("author","狂神说");
    List<Blog> blogs = mapper.queryBlogIf(map);
    System.out.println(blogs);
    session.close();
}

这样写我们可以看到,如果 author 等于 null,那么查询语句为 select from user where title=#{title},但是如果title为空呢?那么查询语句为 select from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!

4.Where 

修改上面的SQL语句;

<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog 
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。【这是我们使用的最多的案例】

5.choose、when、otherwise 

 修改上面的SQL语句;与Java中的switch相似

   <select id="queryBlogIf" parameterType="map" resultType="blog">
        select * from 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>

6.Set 

同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

编写接口方法

int updateBlog(Map map);

sql配置文件

<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
    update blog
      <set>
          <if test="title != null">
              title = #{title},
          </if>
          <if test="author != null">
              author = #{author}
          </if>
      </set>
    where id = #{id};
</update>

测试

@Test
public void testUpdateBlog(){

SqlSession session = MybatisUtils.getSession();

BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();

map.put("title","动态SQL");
map.put("author","秦疆");
map.put("id","9d6a763f5e1347cebda43e2a32687a77");
mapper.updateBlog(map);

   session.close();
}

7.trim定制where、set

8.SQL片段 

 有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

 提取SQL片段:

<sql id="if-title-author">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>

引用SQL片段:

<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
        <include refid="if-title-author"></include>
        <!-- 在这里还可以引用其他的 sql 片段 -->
    </where>
</select>

注意:①、最好基于 单表来定义 sql 片段,提高片段的可重用性

   ②、在 sql 片段中不要包括 where

9.Foreach 

将数据库中前三个数据的id修改为1,2,3;

需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息

编写接口

List<Blog> queryBlogForeach(Map map);

编写SQL语句 

<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <!--
        collection:指定输入对象中的集合属性
        item:每次遍历生成的对象
        open:开始遍历时的拼接字符串
        close:结束时拼接的字符串
        separator:遍历对象之间需要拼接的字符串
        select * from blog where 1=1 and (id=1 or id=2 or id=3)
      -->
        <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
            id=#{id}
        </foreach>
    </where>
</select>

测试

@Test
public void testQueryBlogForeach(){
    SqlSession session = MybatisUtils.getSession();
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    List<Integer> ids = new ArrayList<Integer>();
    ids.add(1);
    ids.add(2);
    ids.add(3);
    map.put("ids",ids);
    List<Blog> blogs = mapper.queryBlogForeach(map);
    System.out.println(blogs);
    session.close();
}

 小结:其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧

十一.MyBatis缓存 

1.简介 

  • 什么是缓存 [ Cache ]?
    • 存在内存中的临时数据。
    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
  • 为什么使用缓存?
    • 减少和数据库的交互次数,减少系统开销,提高系统效率。
  • 什么样的数据能使用缓存?
    • 经常查询并且不经常改变的数据。

2.Mybatis缓存 

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存
    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
    • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

3.一级缓存 

  • 一级缓存也叫本地缓存:
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

在mybatis中加入日志,方便测试结果

 编写接口方法

//根据id查询用户
User queryUserById(@Param("id") int id);

 接口对应的Mapper文件

<select id="queryUserById" resultType="user">
    select * from user where id = #{id}
</select>

测试

 @Test
    public void selectUserById() {
        SqlSession session = MybatisUtils.getSession(); // 一级缓存开始

        UserMapper userMapper = session.getMapper(UserMapper.class);

        User user1 = userMapper.selectUserById(1);
        System.out.println(user1);

        System.out.println("-----------------------------");

        User user2 = userMapper.selectUserById(1);
        System.out.println(user2);

        System.out.println("------------------------------");
        System.out.println(user1 == user2);

        session.close(); // 一级缓存结束
        //一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它,这里指的是缓存范围
        
}

结果分析

一级缓存失效的四种情况 

一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

1.sqlSession不同

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

观察结果:发现发送了两条SQL语句! 

结论:每个sqlSession中的缓存相互独立

 2.sqlSession相同,查询条件不同

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

观察结果:发现发送了两条SQL语句!很正常的理解 

结论:当前缓存中,不存在这个数据

3.sqlSession相同,两次查询之间执行了增删改操作!

//修改用户
int updateUser(Map map);
<update id="updateUser" parameterType="map">
    update user set name = #{name} where id = #{id}
</update>
@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.queryUserById(1);
    System.out.println(user);
    HashMap map = new HashMap();
    map.put("name","kuangshen");
    map.put("id",4);
    mapper.updateUser(map);
    User user2 = mapper.queryUserById(1);
    System.out.println(user2);
    System.out.println(user==user2);
    session.close();
}

 观察结果:查询在中间执行了增删改操作后,重新执行了

结论:因为增删改操作可能会对当前数据产生影响

4.sqlSession相同,手动清除一级缓存 

@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.queryUserById(1);
    System.out.println(user);
    session.clearCache();//手动清除缓存
    User user2 = mapper.queryUserById(1);
    System.out.println(user2);
    System.out.println(user==user2);
    session.close();
}

 一级缓存就是一个map

4.二级缓存 

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

 使用步骤

开启全局缓存 【mybatis-config.xml】

<setting name="cacheEnabled" value="true"/>

 去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】

<cache/>

<!-- 可以带参数,也可以不带参数 -->

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

所有的实体类先实现序列化接口

@Data
@NoArgsConstructor
@AllArgsConstructor
@Alias("user")
public class User implements Serializable {
    private int id;  //id
    private String name;   //姓名
    private String pwd;
}

 测试代码

    @Test
    public void selectUserById() {
        SqlSession session1 = MybatisUtils.getSession(); // session1一级缓存开启
        SqlSession session2 = MybatisUtils.getSession();//  session2一级缓存开启

        UserMapper userMapper1 = session1.getMapper(UserMapper.class);
        UserMapper userMapper2 = session2.getMapper(UserMapper.class);

        User user1 = userMapper1.selectUserById(1);
        System.out.println(user1);
        session1.close(); // session1关闭一级缓存,将一级缓存的信息保存在二级缓存中

        System.out.println("session1-----------------------------");

        User user2 = userMapper2.selectUserById(1);
        System.out.println(user2);

        System.out.println("session2------------------------------");
        System.out.println(user1 == user2);
        session2.close();// session2关闭一级缓存,将一级缓存的信息保存在二级缓存中

    }

结论

  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
  • 查出的数据都会被默认先放在一级缓存中
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

5.缓存原理 

6.EhCache

  • Ehcache是一种广泛使用的java分布式缓存,用于通用缓存;

  • 要在应用程序中使用Ehcache,需要引入依赖的jar包

 https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache

编写ehcache.xml文件,如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
</ehcache>

在mapper.xml中使用对应的缓存即可

 <cache type="org.mybatis.caches.ehcache.EhBlockingCache"/>

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐