从防抖节流到事件派发:一个定时器搞定click与dblclick的‘相爱相杀’

写小说的闲鱼牧云

从防抖节流到事件派发:一个定时器搞定click与dblclick的“相爱相杀”

在构建复杂交互界面时,开发者常会遇到一个经典难题:如何优雅地区分单击(click)和双击(dblclick)事件?这个问题看似简单,却涉及浏览器事件机制、定时器控制和设计模式等多个技术维度。当我们需要开发图形编辑器、游戏UI或自定义组件库时,精确控制这两种基础交互行为往往成为用户体验的关键所在。

传统解决方案通常采用固定延迟的定时器来区分两种事件,但这种方法在动态交互场景中往往显得生硬且不可靠。本文将带你从浏览器事件流原理出发,通过融合防抖(debounce)与节流(throttle)的设计思想,构建一个可配置、高精度的事件派发系统。无论你是开发可视化工具还是复杂表单组件,这套方案都能提供更专业的交互控制能力。

1. 浏览器事件机制深度解析

1.1 事件触发的微观时序

当用户与页面元素交互时,浏览器会按照特定顺序触发一系列事件。对于鼠标操作,这个流程尤为复杂:

code复制mousedown -> mouseup -> click
          -> mousedown -> mouseup -> click -> dblclick

关键时间参数:

  • 现代浏览器默认将双击间隔阈值设为300ms左右
  • 移动端设备通常会延长这个阈值(约500ms)以适应触摸操作
  • 操作系统级别的双击速度设置也会影响这个阈值

注意:不同浏览器对连续点击的时间判定存在细微差异,在跨平台开发时需要特别关注。

1.2 事件冒泡与捕获的特殊性

双击事件的处理还需要考虑DOM事件流的特性:

  1. 捕获阶段:从window对象向下传播到目标元素
  2. 目标阶段:在目标元素上触发事件
  3. 冒泡阶段:从目标元素向上传播回window

这种传播机制意味着:

  • 在父元素上绑定的事件监听器也会收到子元素的click/dblclick事件
  • 调用stopPropagation()可能破坏预期的双击检测逻辑
  • 事件委托模式需要特殊处理才能正确识别双击

2. 传统解决方案的局限性

2.1 定时器方案的基本实现

最常见的解决方案是通过setTimeout延迟click事件处理:

javascript复制let clickTimer = null;

element.addEventListener('click', (e) => {
  clearTimeout(clickTimer);
  clickTimer = setTimeout(() => {
    handleSingleClick(e);
  }, 250);
});

element.addEventListener('dblclick', (e) => {
  clearTimeout(clickTimer);
  handleDoubleClick(e);
});

这种方案存在几个明显缺陷:

  • 固定延迟难以适应不同用户的点击习惯
  • 快速连续单击可能在延迟阈值边缘产生误判
  • 无法处理更复杂的事件序列(如单击后快速三击)

2.2 性能与响应速度的权衡

下表对比了不同延迟设置的优劣:

延迟时间(ms) 优点 缺点
200 响应迅速 容易误判快速单击
300 平衡性较好 仍可能误判
500 准确度高 明显感知延迟
动态调整 自适应性强 实现复杂度高

3. 进阶事件管理方案设计

3.1 融合防抖与节流思想

我们可以将这个问题抽象为事件流处理模型:

  1. 防抖阶段:在首次点击后启动冷却期,期间新点击会重置计时
  2. 节流阶段:当点击频率超过阈值时,切换为双击识别模式
  3. 状态机过渡:通过有限状态机管理不同交互阶段
javascript复制class ClickManager {
  constructor(options = {}) {
    this.delay = options.delay || 300;
    this.state = 'idle';
    this.timer = null;
  }

  handleClick() {
    switch(this.state) {
      case 'idle':
        this.startPendingState();
        break;
      case 'pending':
        this.transitionToDoubleClick();
        break;
      case 'cooldown':
        // 忽略快速连续点击
        break;
    }
  }

  startPendingState() {
    this.state = 'pending';
    this.timer = setTimeout(() => {
      this.emitSingleClick();
      this.state = 'idle';
    }, this.delay);
  }

  transitionToDoubleClick() {
    clearTimeout(this.timer);
    this.emitDoubleClick();
    this.state = 'cooldown';
    setTimeout(() => {
      this.state = 'idle';
    }, 100);
  }
}

3.2 可配置的事件派发器

基于上述原理,我们可以构建一个更健壮的解决方案:

javascript复制function createEventDispatcher(config) {
  const {
    delay = 300,
    onSingleClick = () => {},
    onDoubleClick = () => {},
    maxClicks = 2
  } = config;

  let clickCount = 0;
  let lastClickTime = 0;
  let timer = null;

  return function handleEvent(e) {
    const now = Date.now();
    const isFastClick = now - lastClickTime < delay;

    lastClickTime = now;
    clickCount = isFastClick ? clickCount + 1 : 1;

    clearTimeout(timer);

    if (clickCount >= maxClicks) {
      onDoubleClick(e);
      clickCount = 0;
      return;
    }

    timer = setTimeout(() => {
      if (clickCount === 1) {
        onSingleClick(e);
      }
      clickCount = 0;
    }, delay);
  };
}

// 使用示例
const button = document.getElementById('action-btn');
button.addEventListener('click', createEventDispatcher({
  onSingleClick: () => console.log('Single action'),
  onDoubleClick: () => console.log('Double action'),
  delay: 250
}));

4. 实战优化与高级技巧

4.1 动态延迟校准技术

通过分析用户的实际点击模式,我们可以动态调整延迟阈值:

javascript复制const dynamicDelay = (baseDelay, history = []) => {
  if (history.length < 3) return baseDelay;
  
  const avgInterval = history
    .slice(-3)
    .reduce((sum, [,interval]) => sum + interval, 0) / 3;
  
  return Math.min(
    baseDelay * 1.5,
    Math.max(
      baseDelay * 0.7,
      avgInterval * 0.8
    )
  );
};

# 1. 概述

## 1.1 什么是MyBatis

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

## 1.2 持久化

数据持久化

- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存:**断电即失**
- 数据库(jdbc),io文件持久化

## 1.3 持久层

Dao层、Service层、Controller层

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

## 1.4 为什么需要MyBatis

- 帮助程序员将数据存入到数据库中
- 方便
- 传统的JDBC代码太复杂了。简化、框架、自动化
- 不用MyBatis也可以。更容易上手
- 优点:
  - 简单易学
  - 灵活
  - sql和代码的分离,提高了可维护性
  - 提供映射标签,支持对象与数据库的orm字段关系映射
  - 提供对象关系映射标签,支持对象关系组建维护
  - 提供xml标签,支持编写动态sql

# 2. 第一个MyBatis程序

思路:搭建环境 --> 导入MyBatis --> 编写代码 --> 测试

## 2.1 搭建环境

搭建数据库

```sql
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
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
(1,'狂神','123456'),
(2,'张三','123456'),
(3,'李四','123456')

新建项目

  1. 新建一个普通的maven项目
  2. 删除src目录
  3. 导入maven依赖
xml复制<!--导入依赖-->
<dependencies>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

2.2 创建一个模块

  • 编写mybatis的核心配置文件
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核心配置文件-->
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/kuang/dao/UserMapper.xml"/>
    </mappers>
</configuration>
  • 编写mybatis工具类
java复制//sqlSessionFactory --> sqlSession
public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

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

    //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
    // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }

}

2.3 编写代码

  • 实体类
java复制//实体类
public class User {
    private int id;
    private String name;
    private String pwd;

    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  • Dao接口
java复制public interface UserDao {
    List<User> getUserList();
}
  • 接口实现类由原来的UserDaoImpl转变为一个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.kuang.dao.UserDao">
    <!--select查询语句-->
    <select id="getUserList" resultType="com.kuang.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

2.4 测试

注意点:

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

MapperRegistry是什么?

核心配置文件中注册mappers

  • junit测试
java复制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();
    }

}

可能会遇到的问题:

  1. 配置文件没有注册
  2. 绑定接口错误
  3. 方法名不对
  4. 返回类型不对
  5. Maven导出资源问题

3. CRUD

1. namespace

namespace中的包名要和Dao/mapper接口的包名一致

2. select

选择,查询语句;

  • id:就是对应的namespace中的方法名;
  • resultType:Sql语句执行的返回值!
  • parameterType:参数类型!
  1. 编写接口
java复制//根据ID查询用户
User getUserById(int id);
  1. 编写对应的mapper中的sql语句
xml复制<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
    select * from mybatis.user where id = #{id}
</select>
  1. 测试
java复制@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();
}

3. Insert

xml复制<!--对象中的属性,可以直接取出来-->
<insert id="addUser" parameterType="com.kuang.pojo.User">
    insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd});
</insert>

4. update

xml复制<update id="updateUser" parameterType="com.kuang.pojo.User">
    update mybatis.user set name=#{name},pwd=#{pwd}  where id = #{id} ;
</update>

5. Delete

xml复制<delete id="deleteUser" parameterType="int">
    delete from mybatis.user where id = #{id};
</delete>

注意点:

  • 增删改需要提交事务!

6. 分析错误

  • 标签不要匹配错
  • resource绑定mapper,需要使用路径!
  • 程序配置文件必须符合规范!
  • NullPointerException,没有注册到资源!
  • 输出的xml文件中存在中文乱码问题!
  • maven资源没有导出问题!

7. 万能Map

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

java复制//万能的Map
int addUser2(Map<String,Object> map);
xml复制<!--对象中的属性,可以直接取出来    传递map的key-->
<insert id="addUser2" parameterType="map">
    insert into mybatis.user (id, pwd) values (#{userid},#{passWord});
</insert>
java复制@Test
public void addUser2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);


    Map<String, Object> map = new HashMap<String, Object>();
    map.put("userid",5);
    map.put("passWord","2222333");

    mapper.addUser2(map);

    sqlSession.commit();
    sqlSession.close();
}

Map传递参数,直接在sql中取出key即可!【parameterType="map"】

对象传递参数,直接在sql中取对象的属性即可!【parameterType="Object"】

只有一个基本类型参数的情况下,可以直接在sql中取到!

多个参数用Map,或者注解!

8. 思考题

模糊查询怎么写?

  1. Java代码执行的时候,传递通配符% %
java复制List<User> userList = mapper.getUserLike("%李%");
  1. 在sql拼接中使用通配符!
xml复制select * from mybatis.user where name like "%"#{value}"%"

4. 配置解析

1. 核心配置文件

  • mybatis-config.xml

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

xml复制configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

2. 环境配置(environments)

MyBatis 可以配置成适应多种环境

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

学会使用配置多套运行环境!

MyBatis默认的事务管理器就是JDBC,连接池:POOLED

3. 属性(properties)

我们可以通过properties属性来实现引用配置文件

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

编写一个配置文件

db.properties

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

在核心配置文件中映入

xml复制<!--引入外部配置文件-->
<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="pwd" value="11111"/>
</properties>
  • 可以直接引入外部文件
  • 可以在其中增加一些属性配置
  • 如果两个文件有同一个字段,优先使用外部配置文件的!

4. 类型别名(typeAliases)

  • 类型别名可为 Java 类型设置一个缩写名字。
  • 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
xml复制<!--可以给实体类起别名-->
<typeAliases>
    <typeAlias type="com.kuang.pojo.User" alias="User"/>
</typeAliases>

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

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

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

在实体类比较少的时候,使用第一种方式。

如果实体类十分多,建议使用第二种。

第一种可以DIY别名,第二种则不行,如果非要改,需要在实体上增加注解

java复制@Alias("user")
public class User {}

5. 设置(settings)

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

image-20200706163624448

image-20200706163642010

6. 其他配置

7. 映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件;

方式一:【推荐使用】

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

方式二:使用class文件绑定注册

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

注意点:

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

方式三:使用扫描包进行注入绑定

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

注意点:

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

8. 生命周期和作用域

image-20200706164711010

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

SqlSessionFactoryBuilder:

  • 一旦创建了 SqlSessionFactory,就不再需要它了
  • 局部变量

SqlSessionFactory:

  • 说白了就是可以想象为:数据库连接池
  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
  • 因此 SqlSessionFactory 的最佳作用域是应用作用域。
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession:

  • 连接到连接池的一个请求!
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 用完之后需要赶紧关闭,否则资源被占用!

image-20200706165019010

这里的每一个Mapper,就代表一个具体的业务!

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

1. 问题

数据库中的字段

image-20200706165208010

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

java复制public class User {

    private int id;
    private String name;
    private String password;
}

测试出现问题

image-20200706165319810

xml复制//    select * from mybatis.user where id = #{id}
//类型处理器
//    select id,name,pwd from mybatis.user where id = #{id}

解决方法:

  • 起别名
xml复制<select id="getUserById" resultType="com.kuang.pojo.User">
    select id,name,pwd as password from mybatis.user where id = #{id}
</select>

2. resultMap

结果集映射

code复制id   name   pwd
id   name   password
xml复制<!--结果集映射-->
<resultMap id="UserMap" type="User">
    <!--column数据库中的字段,property实体类中的属性-->
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
</resultMap>

<select id="getUserById" resultMap="UserMap">
    select * from mybatis.user where id = #{id}
</select>
  • resultMap 元素是 MyBatis 中最重要最强大的元素。
  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
  • ResultMap 的优秀之处——你完全可以不用显式地配置它们。
  • 如果这个世界总是这么简单就好了。

6. 日志

6.1 日志工厂

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

曾经:sout、debug

现在:日志工厂!

image-20200706170417010

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

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

STDOUT_LOGGING标准日志输出

在mybatis核心配置文件中,配置我们的日志!

xml复制<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

image-20200706170722010

6.2 Log4j

什么是Log4j?

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
  • 我们也可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
  1. 先导入log4j的包
xml复制<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. log4j.properties
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
  1. 配置log4j为日志的实现
xml复制<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>
  1. Log4j的使用!直接测试运行刚才的查询

image-20200706171421012

简单使用

  1. 在要使用Log4j的类中,导入包import org.apache.log4j.Logger;

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

    java复制static Logger logger = Logger.getLogger(UserDaoTest.class);
    
  3. 日志级别

    java复制logger.info("info:进入了testLog4j");
    logger.debug("debug:进入了testLog4j");
    logger.error("error:进入了testLog4j");
    

7. 分页

思考:为什么要分页?

  • 减少数据的处理量

7.1 使用Limit分页

sql复制语法:SELECT * from user limit startIndex,pageSize;
SELECT * from user limit 3;  #[0,n]

使用Mybatis实现分页,核心SQL

  1. 接口
java复制//分页
List<User> getUserByLimit(Map<String,Integer> map);
  1. Mapper.xml
xml复制<!--分页-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
  1. 测试
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();
}

7.2 RowBounds分页

不再使用SQL实现分页

  1. 接口
java复制//分页2
List<User> getUserByRowBounds();
  1. mapper.xml
xml复制<!--分页2-->
<select id="getUserByRowBounds" resultMap="UserMap">
    select * from mybatis.user
</select>
  1. 测试
java复制@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();
}

7.3 分页插件

image-20200706173337710

了解即可,万一以后公司的架构师,说要使用,你需要知道它是什么东西!

8. 使用注解开发

8.1 面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
  • 根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好
  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
  • 接口的本身反映了系统设计人员对系统的抽象理解。
  • 接口应有两类:
    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
  • 一个个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法。
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现。
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构

8.2 使用注解开发

  1. 注解在接口上实现
java复制@Select("select * from user")
List<User> getUsers();
  1. 需要在核心配置文件中绑定接口!
xml复制<!--绑定接口-->
<mappers>
    <mapper class="com.kuang.dao.UserMapper"/>
</mappers>
  1. 测试

本质:反射机制实现

底层:动态代理!

Mybatis详细的执行流程!

8.3 CRUD

我们可以在工具类创建的时候实现自动提交事务!

java复制public static SqlSession  getSqlSession(){
    return sqlSessionFactory.openSession(true);
}

编写接口,增加注解

java复制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);
}

测试类

【注意:我们必须要将接口注册绑定到我们的核心配置文件中!】

关于@Param()注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议大家都加上!
  • 我们在SQL中引用的就是我们这里的@Param()中设定的属性名!

#{} ${}区别

9. Lombok

java复制Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
  • java library
  • plugs
  • build tools
  • with one annotation your class

使用步骤:

  1. 在IDEA中安装Lombok插件!

  2. 在项目中导入lombok的jar包

    xml复制<dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
    </dependency>
    

内容推荐

从APK逆向到安全审计:手把手教你用GDA和jadx分析Android应用(附实战案例)
本文详细介绍了如何使用GDA和jadx工具进行Android应用的逆向分析和安全审计,包括工具安装、基础逆向流程和实战案例。通过分析天气预报应用,揭示常见安全问题如过度权限和数据泄露,并提供自动化脚本和报告生成技巧,帮助开发者提升应用安全性。
WSDM 2023-2024时空与时序前沿:从因果推断到异常检测的技术演进与场景落地
本文探讨了WSDM 2023-2024会议中时空与时序数据研究的最新进展,重点介绍了因果推断、不确定性建模和异常检测等技术的突破性应用。通过CityCAN、CreST和MultiSPANS等论文案例,展示了这些技术在智慧交通、物流规划和医疗监测等场景中的实际价值,为数据挖掘领域的从业者提供了前沿技术落地的实用指南。
从LVDS到CML:手把手解析SerDes接口里的那些‘模拟电路’(附CDR与PLL工作原理)
本文深入解析SerDes接口中的关键模拟电路,包括LVDS与CML的差分信号技术、PLL时钟生成及CDR数据捕获原理。通过详细电路模型和性能对比,揭示高速串行通信背后的核心技术,助力工程师优化SerDes设计,应对112Gbps及以上速率的挑战。
Rainmeter插件开发入门:手把手教你写一个获取网络数据的股票皮肤
本文详细介绍了Rainmeter插件开发的入门指南,手把手教你如何编写一个获取网络数据的股票皮肤。从开发环境准备到WebParser插件的深度解析,再到实战开发股票数据皮肤,涵盖了Rainmeter插件开发的核心技术和实用技巧,帮助开发者快速掌握网络数据监控皮肤的创建方法。
别再让模型原地踏步了!手把手教你用Cesium的lerp函数实现车辆平滑轨迹动画
本文详细介绍了如何使用Cesium的lerp函数实现车辆平滑轨迹动画,解决三维场景中模型移动卡顿、跳跃的问题。通过线性插值原理、智能插值算法和高级优化技巧,帮助开发者打造丝滑的实时轨迹效果,提升三维可视化体验。
别再被AUTOSAR官方文档绕晕了!用宿舍开黑的故事,5分钟搞懂CanNM网管报文
本文通过宿舍开黑游戏的生动比喻,深入浅出地解析了AUTOSAR CanNM网管报文的核心机制。从节点休眠、报文唤醒到网络同步保持和异常处理,用生活场景类比车载网络管理技术,帮助工程师快速理解复杂的CanNM协议,摆脱官方文档的晦涩难懂。
别再傻傻分不清了!FPGA项目里RAM、ROM、FIFO到底怎么选?用Spartan-6开发板实测告诉你
本文深入探讨FPGA项目中RAM、ROM与FIFO的选择策略,基于Spartan-6开发板的实测数据,提供存储器选型的黄金法则。从易失性、时序特性和资源占用三个维度分析各类存储器的优劣,并给出高速数据采集、低功耗物联网等典型场景的优化方案,帮助开发者避免常见陷阱,提升FPGA项目性能。
CTF实战解析:从Base64隐写术到信息隐藏的攻防艺术
本文深入解析CTF竞赛中的Base64隐写术,从编码原理到实战技巧,详细介绍了如何利用填充位隐藏信息。通过BUUCTF和ACTF等赛事案例,分享自动化脚本开发与攻防对抗经验,帮助安全从业者掌握信息隐藏的检测与防御方法。
Spring Boot配置加密实战:从Jasypt原理到自定义PropertySource代理
本文深入探讨了Spring Boot配置加密的实战方法,从Jasypt的集成原理到自定义PropertySource代理的实现。通过详细的代码示例和最佳实践,帮助开发者安全地管理敏感配置信息,提升应用安全性。文章还涵盖了密钥管理、性能优化和常见问题排查等高级话题。
跨越物理界限:MODBUS RTU Over TCP/IP 的工业网络融合实践
本文深入探讨了MODBUS RTU Over TCP/IP在工业网络中的融合实践,详细解析了协议转换的底层原理、实战配置流程及性能优化技巧。通过实际案例展示了如何突破传统MODBUS RTU的物理距离限制,实现老旧设备与现代系统的无缝对接,显著提升工业网络的灵活性和效率。
VMware里装Redhat 8.6,我移除了USB和打印机后,系统性能居然有这些变化
本文通过VMware Workstation在Redhat 8.6上的实验,展示了移除USB控制器和虚拟打印机等默认硬件设备对系统性能的显著提升。测试数据显示,启动时间缩短13.5%,内存占用减少13.5%,I/O性能提升4.6%-4.7%,为虚拟机优化提供了实用指南。
从PC到手机:聊聊高通骁龙平台上的安卓UEFI启动那些事儿
本文深度解析了高通骁龙平台上的安卓UEFI启动架构,探讨了UEFI技术如何从PC领域扩展到移动设备。文章详细介绍了XBL与ABL的协作机制、移动端UEFI的五大适应性改造,以及定制安卓UEFI的实战场景,为开发者提供了全面的技术指南。
JFlash实战:从零开始为冷门MCU添加支持并烧录固件
本文详细介绍了如何使用JFlash工具为冷门MCU添加支持并烧录固件的完整流程。从硬件环境搭建、芯片关键信息获取到算法文件提取与处理,再到修改JLinkDevices.xml配置文件,最后完成固件烧录。文章特别强调了烧录过程中的常见问题及解决方案,适合嵌入式开发者在面对非标准MCU时的参考。
ARFF文件解析:从概念到实战,解锁Weka数据挖掘的格式密码
本文深入解析ARFF文件格式,从基础概念到实战应用,详细讲解其在Weka数据挖掘中的核心作用。通过剖析文件结构、对比CSV格式及分享高级技巧,帮助读者掌握ARFF文件的编写规范与优化策略,提升数据预处理效率。
避坑指南:Prometheus监控MySQL时,mysqld_exporter权限配置与安全组那些事儿
本文详细解析了Prometheus监控MySQL时常见的权限配置与安全组问题,特别是mysqld_exporter的精细权限控制、配置文件安全隐患及云平台网络隔离的解决方案。通过实战案例和检查清单,帮助技术团队避开监控部署中的典型陷阱,确保数据库监控系统的安全与稳定。
RHEL 8 文本模式安装实战:从零到一构建无图形界面的Linux服务器
本文详细介绍了RHEL 8文本模式安装的实战步骤,从准备工作到安装流程、关键配置及安装后优化,帮助用户高效构建无图形界面的Linux服务器。特别适合老旧硬件或远程管理场景,通过文本模式安装实现轻量级系统部署,提升服务器性能和稳定性。
贝叶斯网络实战:从零构建与概率推断全解析
本文详细解析了贝叶斯网络从构建到概率推断的全过程,包括智能诊断系统、工业设备故障预警等实战应用。通过Python代码示例和工程化技巧,帮助开发者掌握贝叶斯网络在人工智能领域的核心应用,提升不确定性推理能力。
拆解一块TFT-LCD屏幕:聊聊给像素“供电”的5路电源都是怎么来的
本文深入拆解TFT-LCD屏幕的电源系统,详细解析5路关键电压(VDD、AVDD、VGH、VGL、VCOM)的生成原理与电路设计。通过实物拆解和示波器测量,揭示Power IC如何协同工作,为像素精确供电,并探讨现代集成化PMIC方案的技术演进与能效优势。
【CP2K】从入门到实践:一份面向计算化学新手的生存指南
本文为计算化学新手提供了一份CP2K软件的全面生存指南,从环境搭建、输入文件解析到性能调优和常见问题解决。详细介绍了CP2K作为'计算化学瑞士军刀'的核心优势,包括GPW算法、Quickstep模块等特性,并分享了实战参数配置和高效学习路径,帮助读者快速掌握这一强大工具。
Keras预测性能优化:model()与predict()的实战选择与效率对比
本文深入探讨了Keras中model()与predict()两种预测方法的性能差异与适用场景。通过实测数据对比,揭示了model()在实时推理场景下速度可达predict()的7倍,同时提供了混合精度计算和图模式加速等进阶优化技巧。针对不同应用场景(如大规模实时推理、小批量离线处理、内存敏感型部署),给出了具体的选择建议和最佳实践方案。
已经到底了哦
精选内容
热门内容
最新内容
Stable Diffusion文生图实战:从CLIP编码到VAE解码,一步步拆解AI绘画的‘炼丹’过程
本文深入解析Stable Diffusion文生图技术的完整实现路径,从CLIP文本编码、UNet噪声预测到VAE解码,详细拆解AI绘画的‘炼丹’过程。通过代码示例和技术原理讲解,帮助开发者理解文本生成图像的核心机制,并掌握性能优化与生产部署的关键策略。
用ESP32-C3 DIY一个环境光感应小夜灯:手把手教你ADC采样与GPIO联动(附完整源码)
本文详细介绍了如何利用ESP32-C3和光敏电阻DIY一个智能环境光感应小夜灯,涵盖硬件选型、电路设计、ADC采样、FreeRTOS任务调度等关键技术。通过手把手教程和完整源码,帮助开发者快速掌握嵌入式开发中的模拟信号采集与GPIO联动,实现低功耗、自动调光的实用物联网设备。
从译码到驱动:74系列经典芯片实战指南与典型电路解析
本文深入解析74系列经典芯片(如74LS138、74HC595等)在数字电路设计中的实战应用,涵盖译码器、显示驱动及数据选择等核心功能。通过典型电路示例和代码演示,帮助电子工程师高效解决工业控制、嵌入式系统开发中的常见问题,并分享实用技巧与避坑指南。
告别黑屏:用dd命令和C程序诊断你的Linux帧缓冲设备/dev/fb0
本文深入探讨了Linux帧缓冲设备`/dev/fb0`的黑屏故障诊断方法,通过`dd`命令和C程序实战演示如何快速定位硬件、驱动或配置问题。文章提供了从基础命令行检查到高级编程诊断的完整流程,帮助开发者有效解决显示异常问题。
从零到一:在Visual Studio中为Fortran项目集成Intel MKL库的实战指南
本文详细介绍了在Visual Studio中为Fortran项目集成Intel MKL库的完整流程,从环境准备到项目配置,再到使用PARDISO求解稀疏矩阵的实战示例。通过分步指南和常见问题排查,帮助开发者高效利用MKL库进行高性能计算,提升科学计算应用的开发效率。
别再死记硬背了!用Spring Security 6.x实战项目,带你真正搞懂认证授权流程
本文通过Spring Boot 3.x和Spring Security 6.x实战项目,详细解析了认证授权的核心流程。从基础配置到数据库集成,再到动态权限控制和JWT认证实现,帮助开发者彻底掌握Spring Security的关键技术,解决实际开发中的常见问题。
系统备份翻车实录:从DISM命令报错到成功备份,我踩过的坑都帮你填平了
本文详细记录了使用DISM命令进行Windows系统备份的实战经验,包括常见错误0x80070057的解决方案、配置文件优化技巧及PE环境下的备份策略。通过增量备份和自动化脚本,显著提升备份效率,同时提供性能调优建议,帮助用户避免常见陷阱,实现高效可靠的系统备份。
C# 图像处理性能跃迁:从Bitmap.GetPixel到unsafe指针的实战演进
本文详细探讨了C#图像处理性能优化的三种技术方案:从低效的Bitmap.GetPixel到高效的BitmapData方案,再到终极性能武器unsafe指针操作。通过实战代码和性能对比,展示了如何实现从1200ms到30ms的40倍性能跃迁,特别适合需要实时图像处理的直播美颜、工业检测等场景。
FreeRTOS在STM32L051上的内存捉襟见肘之旅:我是如何用3KB RAM跑起多任务的
本文详细介绍了在STM32L051微控制器上使用FreeRTOS进行内存极限优化的实战经验。通过精确配置FreeRTOS参数、优化任务栈空间、采用Flash存储策略和精简通信机制,成功在仅3KB RAM的资源限制下实现了多任务系统。文章提供了CubeMX配置技巧、栈监控方法和EEPROM优化方案,为物联网设备开发者提供了宝贵的低内存消耗解决方案。
从实战出发:用MSF的socks5代理模块,手把手教你穿透内网(附Proxychains配置)
本文详细介绍了如何利用Metasploit Framework(MSF)构建Socks5代理通道,并结合Proxychains实现内网穿透的实战技术。通过环境准备、路由配置、代理搭建及工具链整合等步骤,为安全从业人员提供了一套完整的企业级内网渗透解决方案,特别适用于红队攻防演练场景。