Mybatis学习笔记

Mybatis

代码集合:Myabits

环境:

  • JDK 19.0.2

  • Mysql 8.0.33

  • Maven 3.9.4

  • IDEA

回顾:

  • JDBC

  • Mysql

  • java基础

  • maven

  • Junit单元测试

官方文档

1. 简介

  • 优秀的持久层框架

  • 定制化SQL

  • 原名iBatis

1.1 如何获得

  • Maven仓库

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

    1.2 持久化

    数据持久化:将程序的数据在持久状态和瞬时状态转化的过程

    为什么要持久化:有一些东西不能丢掉,内存贵

    1.3 持久层

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

    1.4 为什么需要Mybatis

    简化JDBC代码、框架

    帮助程序员把数据存入数据库中

    2. 第一个Mybatis程序

    项目结构如图所示

2.1 搭建环境

搭建数据库

CREATE DATABASE `mybatis`;

USE `mybatis`;

CREATE TABLE `user`(
	`id`  INT(20) NOT NULL PRIMARY KEY,
	`name` VARCHAR(30) DEFAULT NULL,
	`pwd`  VARCHAR(30) DEFAULT NULL
)`user`ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `user`(`id`,`name`,`pwd`) VALUES 
(1,'hahahah','1123456'),
(2,'hahah','112345'),
(3,'hah','11234')

新建项目:创建一个普通的maven项目

*使用jdk 1.8

删除src目录

导入maven依赖:mysql\mybatis\junit

<!--导入依赖-->
<dependencies>
  <!--mysql驱动-->
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.46</version>
  </dependency>
  <!--mybatis-->
  <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
  <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.13</version>
  </dependency>
  <!--junit-->
  <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
  </dependency>
  <!--lombok-->
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.10</version>
  </dependency>
</dependencies>

2.2 导入Mybatis

  • 编写mybatis的核心配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://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?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
  • 编写Mybatis工具类

    package org.example.utils;
    ​
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.*;
    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;
        //使用Mybatis第一步:获取sqlSessionFactory对象
        static {
            try{
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e){
                e.printStackTrace();
            }
        }
        //获得SqlSession实例:包含了面向数据库执行SQL命令所需的所有方法
        public static SqlSession getSqlSession(){
            return sqlSessionFactory.openSession();
        }
    }

2.3 编写代码

  • 实体类

    package org.example.pojo;
    //lombok:使用注解可以简化一些必须有但又显得臃肿的代码,比如set和get方法,属性较多时,一堆的get和set方法占用很多行,影响阅读性
    @Data
    //实体类
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
  • Dao接口

    package mybatis.demo.dao;
    import mybatis.demo.pojo.User;
    import java.util.List;
    public interface UserDao {//Dao等价于之后的Mapper
        List<User> getUserList();
        //查询全部用户
        List<User> getUserList();
    }
  • 接口实现类

    package mybatis.demo.dao;
    import mybatis.demo.pojo.User;
    import java.util.List;
    public interface UserDao {//Dao等价于之后的Mapper
        List<User> getUserList();
        //查询全部用户
        List<User> getUserList();
    }

2.4 测试

注意点:org.apache.ibatis.binding.BindinException:Type interface mybatis.demo.dao.UserDao is not known to the MapperRegistry.【修改之后要reload那个pom.xml文件】

  • junit测试

    package org.example.dao;
    ​
    import org.example.utils.MybatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    import org.example.pojo.User;
    ​
    import java.util.List;
    ​
    public class UserDaoTest {
        @Test
        public void test(){
            //获得SqlSession对象
            SqlSession sqlSession = MybatisUtils.getSqlSession();
    ​
            //执行getMapper
            UserDao userDao = sqlSession.getMapper(UserDao.class);
            List<User> userList = userDao.getUserList();
    ​
            for(User user : userList){
                System.out.println(user);
            }
            //关闭SqlSession
            sqlSession.close();
        }
    }

2.5 CRUD增删改查

2.5.1 namespace

namespace中的包名要和接口(Dao/Mapper)的包名一致

2.5.2 select

选择查询语句:

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

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

  • parameterType:参数类型

需要修改的文件以及代码:

UserMapper.java

package org.example.dao;
​
import org.example.pojo.User;
​
import java.util.List;
import java.util.Map;
​
public interface UserMapper {
​
    List<User> getUserLike(String value);
​
    //查询全部用户
    List<User> getUserList();
​
    //根据ID查询用户
    User getUserById(int id);
​
    //insert一个用户
    int addUser(User user);
​
    //修改用户
    int updateUser(User user);
​
    //删除一个用户
    int deleteUser(int id);
}

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="org.example.dao.UserMapper">
​
    <!--
        select * from mybatis.user where id = ?
        select * from mybatis.user where id = 1 or 1=1
    -->
    <select id="getUserLike" resultType="org.example.pojo.User">
        select * from user where name like "%"#{value}"%"
    </select>
​
    <!--select查询语句-->
    <select id="getUserList" resultType="org.example.pojo.User">
       select * from user
   </select>
​
    <select id="getUserById" resultType="org.example.pojo.User">
        select * from user where id = #{id}
    </select>
​
    <!--对象中的属性,可以直接取出来-->
    <insert id="addUser" parameterType="org.example.pojo.User">
        insert into user (id, name, pwd) values (#{id},#{name},#{pwd});
    </insert>
​
    <update id="updateUser" parameterType="org.example.pojo.User">
        update user set name=#{name},pwd=#{pwd}  where id = #{id} ;
    </update>
​
    <delete id="deleteUser" parameterType="int">
        delete from user where id = #{id};
    </delete>
</mapper>

UserDaoTest.java

package org.example.dao;
​
import org.example.pojo.User;
import org.example.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
​
import java.util.HashMap;
import java.util.List;
import java.util.Map;
​
public class UserDaoTest {
​
    @Test
    public void getUserLike(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserLike("%李%");
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }
​
    @Test
    public void test() {
        //第一步:获得SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            //方式一:getMapper
            UserMapper userDao = sqlSession.getMapper(UserMapper.class);
            List<User> userList = userDao.getUserList();
            for (User user : userList) {
                System.out.println(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭SqlSession
            sqlSession.close();
        }
    }
​
    @Test
    public void getUserById() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1);
        System.out.println(user);
        sqlSession.close();
    }
​
    //增删改需要提交事务
    @Test
    public void addUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int res = mapper.addUser(new User(4, "哈哈", "123333"));
        if (res > 0) {
            System.out.println("插入成功!");
        }
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    }
​
    @Test
    public void updateUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.updateUser(new User(4, "呵呵", "123123"));
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    }
​
    @Test
    public void deleteUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser(4);
        sqlSession.commit();
        sqlSession.close();
    }
}

2.6 Map

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

Map中传递参数,直接在sql中取出key

对象传递参数,直接在sql中取出对象的属性

只有一个基本类型,直接在sql中取到

//不需要知道数据库里有什么,直接去查询
User addUserById2(Map<String,Object> map);
<!--map中的key大小写之类的都无所谓-->
<insert id="addUser2" parameterType="map">
    insert into user(id,name,pwd) values (#{userid},#{userName},#{passwd});
</insert>
public void addUser2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String,Object> map = new Map<String,Object>();
    //其他值如果没有变化或者不需要修改都不用添加,相较于之前的方法来说,对于很多个对象来书不需要new很多个选项,只需要改变需要改变的就行
    map.put("userid",5);
    mapper.addUser2(map);
    sqlSession.close();
}

2.7 模糊查询

java代码执行的时候,传递通配符% %

List<User> userList = mapper.getUserLike("%李%");

在sql拼接中使用通配符

select * from user where name like "%"#{value}"%"
List<User> getUserLike(String value);
<select id="getUserLike" resultType="org.example.pojo.User">
        select * from user where name like "%"#{value}"%"
</select>
<select id="getUserLike" resultType="org.example.pojo.User">
        select * from user where name like "%"#{value}"%"
</select>

3. 配置

3.1 属性优化

3.1.1核心配置文件

mybatis-config.xml

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

3.1.2 环境配置

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。

尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。

  • 每个数据库对应一个 SqlSessionFactory 实例

    //为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
    //如果忽略了环境参数,那么将会加载默认环境,如下所示:
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
  • environments 元素定义了如何配置环境

    • 默认使用的环境 ID(比如:default="development")。

    • 每个 environment 元素定义的环境 ID(比如:id="development")。

    • 事务管理器的配置(比如:type="JDBC")。

    • 数据源的配置(比如:type="POOLED")。

    默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。

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

两种类型的事务管理器:JDBC(mybatis默认的事务管理器)、MANAGED

  • JDBC:这个配置直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。默认情况下,为了与某些驱动程序兼容,它在关闭连接时启用自动提交。

    将 "skipSetAutoCommitOnClose" 属性设置为 "true" 来跳过启用自动提交

    <transactionManager type="JDBC">
      <property name="skipSetAutoCommitOnClose" value="true"/>
    </transactionManager>
  • MANAGED:这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。

    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>
3.1.2.2 数据源

连接数据库,mybatis默认的连接池POOLED

3.1.3 属性

通过属性引用配置文件

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

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

修改01代码为02:

编写一个配置文件db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
pwd=root

在核心配置文件中引入:xml中的标签有顺序,properties只能在最上面

<!--引入外部配置文件-->
    <properties resource="db.properties">
        <!--在这里也可以增加属性配置,优先使用外部配置文件db.properties中的信息-->
        <property name="username" value="root"/>
        <property name="pwd" value="11111"/>
    </properties>

3.2 别名优化

类型别名可为 Java 类型设置一个缩写名字。

存在的意义仅在于用来减少类完全限定名的荣誉

<!--可以给实体类起别名-->
<typeAliases>
    <package name="com.kuang.pojo.User" alias="User"/>
</typeAliases>

也可以指定一个包名,Mybatis会在包名下面搜索需要的Java Bean

扫描实体类的包,它的默认别名就是这个类的类名,首字母小写

<!--可以给实体类起别名-->
<typeAliases>
    <package name="com.kuang.pojo"/>
</typeAliases>

在实体类比较少的时候使用第一种方式,如果实体类多使用第二种方式。第一种可以自定义别名,第二种可以加注解@alias

@alias("user")
public class User{}

3.3 设置

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

3.4 其他配置

方式1:

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

方式2:通过class绑定文件注册

接口和mapper配置文件必须同名(在一个包下)

    <!--每一个Mapper.XML都需要在Mybatis核心配置文件中注册!-->
    <mappers>
        <mapper class="com.kuang.dao.UserMapperper/>
    </mappers>

方式3:通过package绑定文件注册

必须同名,在同一包下

    <!--每一个Mapper.XML都需要在Mybatis核心配置文件中注册!-->
    <mappers>
      <package name="com.kuang.dao"/>
    </mappers>

3.5 生命周期和作用域

错误的使用会导致并发问题

SqlSessionFatoryBuilder:

  • 一旦创建SqlSessionFactory,就不再需要它了

  • 局部变量

SqlSessionFactory:

  • 可以想象为数据库的连接池

  • 一旦被创建就应该在应用的运行期间一直存在,没有理由丢弃或者重新创建一个实例

  • 最佳作用域是应用作用域

  • 最简单的是使用单例模式或者静态单例模式

SqlSession:

  • 连接到连接池的一个请求

  • 实例不是线程安全的,不能被共享,最佳作用域是请求或者方法作用域

  • 使用完之后需要关闭,减少资源占用

4. 解决属性名和字段名不一致的问题

新建一个项目,拷贝之前的,测试实体类字段不一致的情况,测试出现问题

// select * from user where id = #{id}
//类型处理器
// select id,name,pwd from user where id = #{id}

解决方法:

  • 起别名

    <select id="getUserById" resultType="com.kuang.pojo.User">
    select id,name,pwd as password from user where id = #{id}
    </select>

4.1 ResultMap结果集映射

id name pwd

id name password

    <!--结果集映射-->
    <resultMap id="UserMap" type="User">
        <!--column数据库中的字段,property实体类中的属性-->
        <!--<result column="id" property="id"/>-->
        <!--<result column="name" property="name"/>-->
        <result column="pwd" property="password"/>
    </resultMap>
  • resultMap元素是Mybatis中最重要最强大的元素

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

 

5. 日志

5.1 日志工厂

如果一个数据库操作出现了异常,需要排错。

曾经:sout、debug

现在:日志工厂

  • SLF4J

  • LOG4J(3.5.9 起废弃) 【掌握】

  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING 【掌握】

  • NO_LOGGING

具体使用哪一个日志实现,在设置中设定。

STDOUT_LOGGING:标准日志输出

mybatis-config.xml(核心配置文件)

    <settings>
        <!--标准的日志工厂实现-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

5.2 Log4j

控制日志信息输送到目的地是控制台 、文件、GUI组件

控制每一条日志的输出格式,通过定义每一条日志信息的级别,控制日志的生成过程

通过一个配置文件进行配置,不需要修改应用的代码

导入Log4j的包

pom.xml

    <dependencies>
        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
编写Properties文件

在CLASSPATH下建立log4j.properties

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][qinjiang%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
配置log4j为日志的实现

mybatis-config.xml

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

直接测试运行刚才的查询

 

简单使用:UserDaoTest.java

在要使用log4j的类中导入包

import org.apache.log4j.Logger;

日志对象,参数为当前的class

static Logger logger = Logger.getLogger(UserDaoTest.class);

日志级别

@Test
public void getUserLike(){
   SqlSession sqlSession = MybatisUtils.getSqlSession();
   logger.info("测试,进入getUserLike方法成功!");
​
   UserMapper mapper = sqlSession.getMapper(UserMapper.class);
   User user = mapper.getUserById(1);
   System.out.println(user);
​
   sqlSession.close();
}
​
@Test
public void testLog4j(){
    logger.info("info:进入了testLog4j");
    logger.debug("debug:进入了testLog4j");
    logger.error("error:进入了testLog4j");
}

6. 分页

减少处理量

6.1 Limit实现分页

6.1.1 语法
SELECT * from user limit startIndex,pageSize;

6.1.2 使用Mybatis实现分页,核心SQL

接口

//分页
List<User> getUserByLimit(Map<String,Integer> map);

UserMapper.xml

<!--分页-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from  user limit #{startIndex},#{pageSize}   
</select>

测试:UserDaoTest.java

   @Test
    public void getUserByLimit(){
        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> userList =  mapper.getUserByLimit(map);
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }

6.2 RowBounds分页

不再使用SQL进行分页

接口

    //分页2
    List<User> getUserByRowBounds();

导入

    <!--分页2-->
    <select id="getUserByRowBounds" resultMap="UserMap">
        select * from  user
    </select>

测试

  
  @Test
    public void getUserByRowBounds(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        //RowBounds实现
        RowBounds rowBounds = new RowBounds(1, 2);
​
        //通过Java代码层面实现分页
        List<User> userList = sqlSession.selectList("com.kuang.dao.UserMapper.getUserByRowBounds",null,rowBounds);
​
        for (User user : userList) {
            System.out.println(user);
        }
​
        sqlSession.close();
    }

6.3 Mybatis分页插件PageHelper

 

 使用方法

7. 使用注解开发

 

7.1 面向接口编程

定义与实现的分离

7.2 使用注解开发

注解在接口上实现

@Selext("select * from user")
List<User> getUsers();

需要核心配置文件中绑定接口

<!--绑定接口-->
<mappers>
    <mapper class="com.kuang.dao.UserMapper"/>
</mappers>

测试

本质:反射机制

底层:动态代理

public class UserMapperTest {
    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //底层主要应用反射
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser(5);
        sqlSession.close();
    }
}

7.3 Mybatis执行流程

7.4 注解增删改查CRUD

关于@Param()的注解

  • 基本类型的参数或者String类型,需要加上

  • 引用类型不需要加

  • 如果只有一个基本类型的话,可以忽略

  • 在SQL中引用的就是@Param()中设定的属性名

在工具类创建的时候实现自动提交事务

public interface UserMapper {
    @Select("select * from user")
    List<User> getUsers();
    // 方法存在多个参数,所有的参数前面必须加上 @Param("id")注解
    @Select("select * from user where id = #{id}")
    User getUserByID(@Param("id") int id);
​
    @Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
    int addUser(User user);
​
    @Update("update user set name=#{name},pwd=#{password} where id = #{id}")
    int updateUser(User user);
​
    @Delete("delete from user where id = #{uid}")
    int deleteUser(@Param("uid") int id);
}

测试

User userByID = mapper.getUserByID(1);
        System.out.println(userByID);
        mapper.addUser(new User(5,"Hello","123123"));
​
        mapper.updateUser(new User(5,"to","21312"));

7.5 Lombok

通过注解的形式自动生成构造器

ProjectLombok

 

  • java library

  • plugs

  • build tools

  • with one annocation your class

7.5.1 使用步骤

在IDEA中安装Lombok插件

在项目中导入lambok的jar包

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
 1|@Getter and @Setter
 2|@FieldNameConstants
 3|@ToString
 4|@Data
 5|@EqualsAndHashCode
 6|@SneakyThrows
 7|@Wither
 8|@Accessors
 9|@Value
10|@Delegate
11|@Singular
12|@Builder
13|@Log,@Log4j,@Log4j2,@slf4j,@xslf4j,@CommonsLog,@JBossLog,@Flogger
14|@AllArgsConstructor,@RequireArgsConstructor

在实体类上加上注解

package com.kuang.pojo;
import lombok.*;
@Data  //无参构造,get set hashcode tostring
@AllArgsConstructor //
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String password;
}

8. 复杂查询环境搭建

8.1多对一的处理

多个学生对应一个老师

8.1.1 创建数据库

新的表teacher和student

student中的tid:指向teacher这个表当中id为tid的老师

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, 'teacher');
CREATE TABLE `student` (
    `id` INT(10) NOT NULL,
    `name` VARCHAR(30) DEFAULT NULL,
    `tid` INT(10) DEFAULT NULL,
    PRIMARY KEY (`id`),`student`
    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',1);
INSERT INTO student (`id`, `name`, `tid`) VALUES (2, '学生2',1);
INSERT INTO student (`id`, `name`, `tid`) VALUES (3, '学生3',1);
INSERT INTO student (`id`, `name`, `tid`) VALUES (4, '学生4',1);
INSERT INTO student (`id`, `name`, `tid`) VALUES (5, '学生5',1);

8.1.2 搭建测试环境

  • IDEA安装Lombok插件

  • 引入Maven依赖

        <dependencies>
            <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.10</version>
            </dependency>
        </dependencies>
  • 在代码中增加注解

        <dependencies>
            <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.10</version>
            </dependency>
        </dependencies>
    package com.kuang.pojo;
    import lombok.Data;
    ​
    @Data
    public class Student {
        private int id;
        private String name;
        //学生需要关联一个老师!
        private Teacher teacher;
    }
  • 编写实体类对应的Mapper接口

    package com.kuang.dao;
    import com.kuang.pojo.Student;
    import java.util.List;
    public interface StudentMapper {
    ​
    }
    package com.kuang.dao;
    ​
    import com.kuang.pojo.Teacher;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    ​
    public interface TeacherMapper {
    ​
    }
  • 编写Mapper接口对应的mapper.xml配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    ​
    <mapper namespace="com.kuang.dao.StudentMapper">
    ​
    </mapper>
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    ​
    <mapper namespace="com.kuang.dao.TeacherMapper">
    ​
    </mapper>
8.1.3 按查询嵌套处理

SQL中的子查询

  • 给StudentMapper接口增加方法

        //查询所有的学生信息,以及对应的老师的信息!
        public List<Student> getStudent();
  • 编写对应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.kuang.mapper.StudentMapper">
    ​
       <!--
       需求:获取所有学生及对应老师的信息
       思路:
           1. 获取所有学生的信息
           2. 根据获取的学生信息的老师ID->获取该老师的信息
           3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询?
               1. 做一个结果集映射:StudentTeacher
               2. StudentTeacher结果集的类型为 Student
               3. 学生中老师的属性为teacher,对应数据库中为tid。
                  多个 [1,...)学生关联一个老师=> 一对一,一对多
               4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询
       -->
       <select id="getStudents" resultMap="StudentTeacher">
        select * from student
       </select>
       <resultMap id="StudentTeacher" type="Student">
           <!--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

    <resultMap id="StudentTeacher" type="Student">
       <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
       <association property="teacher"  column="{id=tid,name=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} and name = #{name}
    </select>
  • 测试

    @Test
    public void testGetStudents(){
       SqlSession session = MybatisUtils.getSession();
       StudentMapper mapper = session.getMapper(StudentMapper.class);
    ​
       List<Student> students = mapper.getStudents();
    //通过遍历学生列表,分别输出每个学生的姓名和对应的老师姓名
       for (Student student : students){
           System.out.println(
                   "学生名:"+ student.getName()
                           +"\t老师:"+student.getTeacher().getName());
      }
    }
8.1.4 按结果嵌套处理

SQL中的联表查询

  • 接口方法编写

    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文件中注入

    <?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核心配置文件-->
    <configuration>
        <!--引入外部配置文件-->
        <properties resource="db.properties"/>
    ​
        <settings>
            <!--标准的日志工厂实现-->
            <setting name="logImpl" value="STDOUT_LOGGING"/>
        </settings>
    ​
        <!--可以给实体类起别名-->
        <typeAliases>
            <package name="com.kuang.pojo"/>
        </typeAliases>
    ​
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${pwd}"/>
                </dataSource>
            </environment>
        </environments>
    ​
        <mappers>
            <mapper class="com.kuang.dao.TeacherMapper"/>
            <mapper class="com.kuang.dao.StudentMapper"/>
        </mappers>
    </configuration>
  • 测试

    @Test
    public void testGetStudents2(){
       SqlSession session = MybatisUtils.getSession();
       StudentMapper mapper = session.getMapper(StudentMapper.class);
    ​
       List<Student> students = mapper.getStudents2();
    ​
       for (Student student : students){
           System.out.println(
                   "学生名:"+ student.getName()
                           +"\t老师:"+student.getTeacher().getName());
      }
    }

8.2 一对多的处理

一个老师对应多个学生

  • 关联 association:用于一对一和多对一

  • 集合 collection:一对多

  • javaType和ofType指定对象类型

    • JavaType指定pojo中的属性类型

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

 

8.2.1 实体类的编写
@Data
public class Student{
    private int id;
    private String name;
    private int id;
}
@Data
public class Teacher{
    private int id;
    private String name;
    //一个老师多个学生
    private List<Student> student;
}
8.2.2 搭建测试环境

导入lombok->接口->接口对应的xml文件

package com.kuang.dao;
import com.kuang.pojo.Student;
import java.util.List;
public interface StudentMapper {
}
package com.kuang.dao;
import com.kuang.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface TeacherMapper {
​
}
8.2.3 按结果嵌套处理
  • TeacherMapper接口编写方法

    //获取指定老师以及老师下所有学生
    public Teacher getTeacher(int id);
  • 编写接口对应的Mapper配置文件

    <mapper namespace="com.kuang.mapper.TeacherMapper">
    ​
       <!--
       思路:
           1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
           2. 对查询出来的操作做结果集映射
               1. 集合的话,使用collection!
                   JavaType和ofType都是用来指定对象类型的
                   JavaType是用来指定pojo中属性的类型
                   ofType指定的是映射到list集合属性中pojo的类型。
       -->
       <select id="getTeacher" resultMap="TeacherStudent">
          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="TeacherStudent" type="Teacher">
           <result  property="name" column="tname"/>
           <!-- •ofType•属性指定了集合元素类型为 •Student•-->
           <collection property="students" ofType="Student">
               <result property="id" column="sid" />
               <result property="name" column="sname" />
               <result property="tid" column="tid" />
           </collection>
       </resultMap>
    </mapper>
  • 将Mapper文件注册到Mybatis-config.xml文件中

    <mappers>
        <mapper resource="mapper/TeacherMapper.xml"/>
    </mappers>
  • 测试

    @Test
    public void testGetTeacher(){
        SqlSession session = MybatisUtils.getSession();
        TeacherMapper mapper = session.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacher(1);
        System.out.println(teacher.getName());
        System.out.println(teacher.getStudents());
    }
8.2.4 按查询嵌套处理
  • TeacherMapper接口编写方法

    public Teacher getTeacher2(int id);
  • 编写接口对应的Mapper配置文件

    <select id="getTeacher2" resultMap="TeacherStudent2">
    select * from teacher where id = #{id}
    </select>
    <resultMap id="TeacherStudent2" 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文件中

  • 测试

    @Test
    public void testGetTeacher2(){
        SqlSession sqlSession = MybatisUtils.getSession();
        TeacherMapper mapper = session.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacher2(1);
        System.out.println(teacher.getName());
        System.out.println(teacher.getStudents());
    }

9. 动态SQL

9.1 简介

  • 动态sql指的是根据不同的查询条件生成不同的sql语句

  • 使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。

9.2 环境搭建

9.2.1 新建数据库表 blog

字段: id, title, author, creat_time, views

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
9.2.2 创建Mybatis基础工程

 

package com.kuang.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;

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

    public static SqlSession  getSqlSession(){
        return sqlSessionFactory.openSession(true);
    }
}
9.2.3 IDutil工具类
public class IDUtil{
    public static String genld(){
        return UUID.randomUUID().toSting().replaceAll("-","");
    }
}
9.2.4 实体类编写
public class IDUtil{
    public static String genld(){
        return UUID.randomUUID().toSting().replaceAll("-","");
    }
}
9.2.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.kuang.mapper.BlogMapper">

</mapper>
9.2.6 mybatis核心配置文件,下划线驼峰自动转换
<settings>
   <setting name="mapUnderscoreToCamelCase" value="true"/>
   <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注册Mapper.xml-->
<mappers>
 <mapper resource="mapper/BlogMapper.xml"/>
</mappers>
9.2.7 插入初始数据
  • 编写接口

    //新增一个博客
    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>
  • 初始化博客方法

    import com.kuang.dao.BlogMapper;
    import com.kuang.pojo.Blog;
    import com.kuang.utils.IDutils;
    import com.kuang.utils.MybatisUtils;
    import org.apache.ibatis.cache.Cache;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    ​
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    ​
    public class MyTest {
        @Test
        public void addInitBlog(){
            SqlSession session = MybatisUtils.getSqlSession();
            BlogMapper mapper = session.getMapper(BlogMapper.class);
    ​
            Blog blog = new Blog();
            blog.setId(IDutils.getId());
            blog.setTitle("Mybatis");
            blog.setAuthor("Leo");
            blog.setCreateTime(new Date());
            blog.setViews(9999);
    //使用 •mapper.addBlog(blog)•方法将该Blog对象插入数据库中。
            mapper.addBlog(blog);
    //通过设置不同的title属性值,重复调用 •mapper.addBlog(blog)•方法向数据库中添加多个博客信息。
            blog.setId(IDutils.getId());
            blog.setTitle("Java");
            mapper.addBlog(blog);
    ​
            blog.setId(IDutils.getId());
            blog.setTitle("Spring");
            mapper.addBlog(blog);
    ​
            blog.setId(IDutils.getId());
            blog.setTitle("微服务");
            mapper.addBlog(blog);
    ​
            session.close();
        }
        @Test
        public void queryBlogIF(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    ​
            HashMap map = new HashMap();
            map.put("author","Leo");
    ​
            List<Blog> blogs = mapper.queryBlogIF(map);
    ​
            for (Blog blog : blogs) {
                System.out.println(blog);
            }
    ​
            sqlSession.close();
        }
    ​
        @Test
        public void queryBlogForEach(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    ​
            HashMap map = new HashMap();
    ​
            ArrayList<Integer> ids = new ArrayList<Integer>();
            ids.add(1);
            ids.add(2);
            ids.add(3);
    ​
            map.put("ids",ids);
            List<Blog> blogs = mapper.queryBlogForeach(map);
    ​
            for (Blog blog : blogs) {
                System.out.println(blog);
            }
            sqlSession.close();
        }
    }

9.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>
  • 测试

    • 如果 author 等于 null,那么查询语句为 select * from user where title=#{title}

    • 如果title为空,查询语句为 select * from user where and author=#{author},是错误的 SQL 语句[查看后面的where语句]

    @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","Leo");
        List<Blog> blogs = mapper.queryBlogIf(map);
        
        System.out.println(blogs);
        session.close();
    }

9.4 常用标签

9.4.1 where语句

修改上面的SQL语句

  • “where”标签:如果它包含的标签中有返回值,插入一个‘where’。

  • 如果标签返回的内容以AND 或OR 开头,会剔除。

<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog
    <!--<if>•标签配合 •<where>•标签实现动态条件查询-->
    <!--在 •<select>•标签内部使用 •<where>•标签来定义查询条件。•<where>•标签会自动处理条件中的逻辑关系(如AND、OR等),对于第一个条件,如果条件成立会添加WHERE关键字,之后的条件会根据情况自动添加AND或者OR关键字。-->
   <where>
       <if test="title != null">
          title = #{title}
       </if>
       <if test="author != null">
          and author = #{author}
       </if>
   </where>
</select>
9.4.2 Set语句
  • 编写接口方法

    int updateBlog(Map map);
  • sql配置文件

    <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","Leo");
        map.put("id","0ae808b526e245c892996c5e3528afb6");
        mapper.updateBlog(map);
        session.close();
    }
9.4.3 choose语句

有时候用不到所有的查询条件,只选择其中一个,即满足一个查询条件即可。choose类似于Java里的switch语句

  • 编写接口方法

    List<Blog> queryBlogChoose(Map map);
  • sql配置文件

    <select id="quertBlogChoose" 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>
  • 测试类

    @Test
    public void testQueryBlogChoose(){
        SqlSession session = MybatisUtils,getSession();
        BlogMapper mapper = session.getMapper(BlogMapper,class);
        
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("title","Java");
        map.put("author","Leo");
        map.put("views",9999);
        List<Blog> blogs = mapper.queryBlogChoose(map);
        System.out.println(blogs);
        session,close();
    }
9.4.4 SQL片段

增加代码重用性,简化代码,抽取代码直接调用

注意:

  • 最好基于单表定义sql片段,提高片段可重用性

  • 在sql片段中不要包括where

  • 提取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>

9.5 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)
         -->
            <!--
           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•,并在 •map•中添加一个键值对,键为"ids",值为一个包含ID列表的ArrayList
        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,quesyBlogForeach(map);
        System.out.println(blogs);
        session.close();
    }

10. 缓存

10.1 简介

什么是缓存

  • 存在内存中的临时数据

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

为什么使用缓存

  • 减少和数据库的交互次数,减少系统开销,提高系统效率

什么样的数据可以使用缓存

  • 经常查询并且不经常改变的数据

10.2 Mybatis缓存

  • 查询缓存特性:定制和配置缓存

  • 默认定义两级缓存

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

    • 二级缓存需要手动开启和配置,基于namespace级别的缓存,也称为本地缓存)

    • 提高扩展性定义缓存接口Cache,通过实现Cache接口自定义二级缓存

10.3 一级缓存

本地缓存:

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

  • 如果需要获取相同的数据,直接从缓存中拿,不用再去查询数据库

  • 一级缓存就是一个map

10.3.1 测试

 

10.4.3 Mybatis缓存原理

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();
}

10.4 二级缓存

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

  • 工作机制

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

    • 当前会话关闭,这个会话对应的一级缓存消失;需求是会话关闭就把一级缓存的数据保存在二级缓存中

    • 新的会话查询信息从二级缓存中获取内容

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

10.4.1 使用步骤

开启全局缓存:mybatis-config.xml

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

去每个mapper.xml中配置使用二级缓存:xxxMapper.xml

<!--在当前Mapper.xml中使用二级缓存-->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
​
    <select id="queryUserById" resultType="user" useCache="false">
        select * from user where id = #{id}
    </select>

下面这个更高级的配置创建了一个FIFO缓存,每隔60秒刷新,最多可以存储结果对象或列表的512个引用,返回的对象被认为是只读。对它们进行修改可能会在不同线程中的调用者产生冲突。

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

代码测试

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

测试代码如下:

@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);
   session.close();
​
   User user2 = mapper2.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);
​
   session2.close();
}
10.4.2 结论
  • 在mybatis中加入日志,方便测试结果

  • 编写接口方法

    //根据id查询用户
    User queryUserById(@Param("id") int id);
  • 接口对应的Mapper文件

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

    @Test
    public void testQueryUserById(){
        SqlSession session = MybatiesUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        //调用 •mapper.queryUserById(1)•方法,参数传入用户ID 1,来查询数据库中ID为1的用户信息。将查询结果存储在 •user•对象中
        User user = mapper.queryUserById(1);
        System.out.println(user);
        User user2 = mapper.queryUserById(1);
        System.out.println(user==user2);
        
        session.close();
    }

    10.3.2 一级缓存失效的四种情况
    • 一级缓存是SqlSession级别的缓存,是一直开启的

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

    sqlSession不同

    发送了两条SQL语句

    结论:每个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();
    }
    sqlSession相同,查询条件不同

    发送了两条SQL查询语句

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

    @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();
    }
    sqlSessino相同。两次查询之间执行了增删改操作

    查询中间执行了增删改操作之后重新执行了

  • 增加方法

    //修改用户
    int updateUser(Map map);
  • 编写SQL

    <!--更新用户信息的示例-->
    <update id="updateUser" parameterType="map">
        <!--在 •<update>•标签中,使用 •id•属性指定了操作的ID,•parameterType•属性指定了参数类型为 •map•。你可以根据实际情况调整参数类型为具体的Java对象。
    ​
    在 •<update>•标签内部,编写了具体的更新语句。这个示例中的更新语句是将 •user•表中 •name•字段的值更新为传入的参数 •name•的值,条件是 •id•字段等于传入的参数 •id•的值。-->
        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();
    }
  • 只要开了二级缓存,在同一个Mapper中查询可以在二级缓存中拿到数据

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

  • 只有会话提交或者关闭,一级缓存的数据才会转入二级缓存

10.4.3 Mybatis缓存原理

10.4.4 自定义缓存Ehche

第三方缓存实现:EhCache,是一种广泛使用的java分布式缓存,用于通用缓存

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

    pom.xml

            <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
            <dependency>
                <groupId>org.mybatis.caches</groupId>
                <artifactId>mybatis-ehcache</artifactId>
                <version>1.1.0</version>
            </dependency>
  • 在mapper.xml中使用对应的缓存

    <mapper namespace="org.acme.FooMapper">
        <cache type = "org.mybatis.caches.ehcache.EhcacheCache"/>
    </mapper>
  • 编写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>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值