深入拆解 MyBatis:Mapper 动态代理、一级与二级缓存的底层实现与实战

简介: 本文深入解析MyBatis三大核心机制:1)Mapper接口通过动态代理实现SQL执行,核心类包括MapperProxy和MapperMethod;2)一级缓存是SqlSession级别的内存缓存,默认开启且基于HashMap实现;3)二级缓存是Mapper级别的可共享缓存,需手动开启且要求实体类实现Serializable。通过代码示例详细演示了缓存的生效条件和失效场景,并对比了一二级缓存的关键差异,帮助开发者深入理解MyBatis底层原理,在实际开发中合理运用缓存机制。

引言

MyBatis作为国内最流行的持久层框架之一,其核心设计精巧且实用。本文将从底层原理出发,结合实战代码,深入拆解MyBatis中最核心的三个机制:Mapper接口的动态代理实现、一级缓存的生效机制以及二级缓存的生效机制。通过本文,你将不仅知其然,更知其所以然,能够在实际开发中灵活运用这些机制解决问题。

一、Mapper接口的动态代理实现

1.1 动态代理的核心概念

在MyBatis中,我们只需要编写Mapper接口,不需要编写实现类,就能直接调用接口方法执行SQL。这背后的核心原理就是JDK动态代理。MyBatis会在运行时为Mapper接口生成一个动态代理对象,当我们调用接口方法时,实际上是调用代理对象的invoke方法,在该方法中完成SQL的解析、参数绑定、执行和结果映射。

1.2 MyBatis动态代理的执行流程

1.3 核心类解析

1.3.1 MapperProxy

MapperProxy是动态代理的核心类,实现了InvocationHandler接口。它持有SqlSession、Mapper接口和方法缓存三个核心对象。当调用代理对象的方法时,会进入invoke方法,该方法会判断是否为Object类的方法(如toString、hashCode),如果是则直接执行;否则会从缓存中获取MapperMethod对象并执行。

1.3.2 MapperMethod

MapperMethod封装了Mapper接口方法的完整信息,包括方法名、参数类型、返回值类型、SQL语句类型等。它的execute方法是SQL执行的入口,会根据SQL类型(SELECT/INSERT/UPDATE/DELETE)调用SqlSession的对应方法完成数据库操作。

1.3.3 MapperProxyFactory

MapperProxyFactory是Mapper代理对象的工厂类,负责创建MapperProxy实例并生成动态代理对象。每个Mapper接口对应一个MapperProxyFactory实例。

1.4 实战示例:从接口调用到SQL执行

1.4.1 项目环境搭建

首先创建一个Maven项目,pom.xml配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>3.2.3</version>
       <relativePath/>
   </parent>
   <groupId>com.jam</groupId>
   <artifactId>mybatis-demo</artifactId>
   <version>1.0.0</version>
   <name>mybatis-demo</name>
   <description>MyBatis核心机制实战演示</description>
   <properties>
       <java.version>17</java.version>
       <mybatis-plus.version>3.5.5</mybatis-plus.version>
       <fastjson2.version>2.0.43</fastjson2.version>
       <swagger.version>2.3.0</swagger.version>
       <guava.version>33.0.0-jre</guava.version>
   </properties>
   <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>${mybatis-plus.version}</version>
       </dependency>
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-generator</artifactId>
           <version>${mybatis-plus.version}</version>
       </dependency>
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>8.0.33</version>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>1.18.30</version>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>com.alibaba.fastjson2</groupId>
           <artifactId>fastjson2</artifactId>
           <version>${fastjson2.version}</version>
       </dependency>
       <dependency>
           <groupId>com.google.guava</groupId>
           <artifactId>guava</artifactId>
           <version>${guava.version}</version>
       </dependency>
       <dependency>
           <groupId>org.springdoc</groupId>
           <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
           <version>${swagger.version}</version>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>
   <build>
       <plugins>
           <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
               <configuration>
                   <excludes>
                       <exclude>
                           <groupId>org.projectlombok</groupId>
                           <artifactId>lombok</artifactId>
                       </exclude>
                   </excludes>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>

application.yml配置如下:

spring:
 datasource:
   driver-class-name: com.mysql.cj.jdbc.Driver
   url: jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
   username: root
   password: root
mybatis-plus:
 configuration:
   log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
   cache-enabled: true
 global-config:
   db-config:
     id-type: auto

SQL脚本如下:

CREATE DATABASE IF NOT EXISTS mybatis_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE mybatis_demo;
CREATE TABLE IF NOT EXISTS `user` (
   `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
   `username` VARCHAR(50) NOT NULL COMMENT '用户名',
   `password` VARCHAR(100) NOT NULL COMMENT '密码',
   `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
   `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   PRIMARY KEY (`id`),
   UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
INSERT INTO `user` (`username`, `password`, `email`) VALUES
('zhangsan', '123456', 'zhangsan@example.com'),
('lisi', '123456', 'lisi@example.com'),
('wangwu', '123456', 'wangwu@example.com');

1.4.2 核心代码实现

实体类User.java:

package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户实体类
* @author ken
*/

@Data
@TableName("user")
@Schema(description = "用户实体")
public class User implements Serializable {
   private static final long serialVersionUID = 1L;
   /**
    * 主键ID
    */

   @TableId(type = IdType.AUTO)
   @Schema(description = "主键ID")
   private Long id;
   /**
    * 用户名
    */

   @Schema(description = "用户名")
   private String username;
   /**
    * 密码
    */

   @Schema(description = "密码")
   private String password;
   /**
    * 邮箱
    */

   @Schema(description = "邮箱")
   private String email;
   /**
    * 创建时间
    */

   @Schema(description = "创建时间")
   private LocalDateTime createTime;
   /**
    * 更新时间
    */

   @Schema(description = "更新时间")
   private LocalDateTime updateTime;
}

Mapper接口UserMapper.java:

package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
* 用户Mapper接口
* @author ken
*/

@Mapper
public interface UserMapper extends BaseMapper<User> {
   /**
    * 根据用户名查询用户
    * @param username 用户名
    * @return 用户信息
    */

   @Select("SELECT * FROM user WHERE username = #{username}")
   User selectByUsername(@Param("username") String username);
}

测试类MybatisProxyTest.java:

package com.jam.demo;
import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* MyBatis动态代理测试类
* @author ken
*/

@Slf4j
@SpringBootTest
public class MybatisProxyTest {
   @Autowired
   private SqlSessionFactory sqlSessionFactory;
   /**
    * 测试Mapper动态代理
    */

   @Test
   public void testMapperProxy() {
       try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
           UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
           log.info("Mapper代理对象类型:{}", userMapper.getClass().getName());
           log.info("Mapper代理对象是否为Proxy:{}", java.lang.reflect.Proxy.isProxyClass(userMapper.getClass()));
           User user = userMapper.selectByUsername("zhangsan");
           log.info("查询结果:{}", user);
       }
   }
}

运行测试类,你会看到如下关键日志:

Mapper代理对象类型:com.sun.proxy.$Proxy87
Mapper代理对象是否为Proxy:true

这证明了UserMapper的实例确实是JDK动态代理生成的对象。

二、一级缓存的生效机制

2.1 一级缓存的定义与范围

一级缓存是SqlSession级别的缓存,默认开启且无法关闭(只能调整缓存范围)。它的作用范围是同一个SqlSession实例,当同一个SqlSession执行相同的SQL查询时,会直接从缓存中获取结果,而不会再次查询数据库。

2.2 一级缓存的执行流程

2.3 一级缓存的底层实现

一级缓存的底层实现类是PerpetualCache,它内部使用一个HashMap来存储缓存数据,key是CacheKey对象(由SQL语句、参数、分页信息等组成),value是查询结果。

2.4 实战示例:一级缓存的生效与失效场景

测试类FirstLevelCacheTest.java:

package com.jam.demo;
import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.support.TransactionTemplate;
/**
* 一级缓存测试类
* @author ken
*/

@Slf4j
@SpringBootTest
public class FirstLevelCacheTest {
   @Autowired
   private SqlSessionFactory sqlSessionFactory;
   @Autowired
   private TransactionTemplate transactionTemplate;
   @Autowired
   private UserMapper userMapper;
   /**
    * 测试一级缓存生效:同一个SqlSession,相同查询
    */

   @Test
   public void testFirstLevelCacheHit() {
       try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
           UserMapper mapper = sqlSession.getMapper(UserMapper.class);
           log.info("第一次查询...");
           User user1 = mapper.selectById(1L);
           log.info("第一次查询结果:{}", user1);
           log.info("第二次查询...");
           User user2 = mapper.selectById(1L);
           log.info("第二次查询结果:{}", user2);
           log.info("两次查询结果是否为同一对象:{}", user1 == user2);
       }
   }
   /**
    * 测试一级缓存失效:不同SqlSession
    */

   @Test
   public void testFirstLevelCacheMissDifferentSession() {
       try (SqlSession sqlSession1 = sqlSessionFactory.openSession();
            SqlSession sqlSession2 = sqlSessionFactory.openSession()) {
           UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
           UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
           log.info("SqlSession1查询...");
           User user1 = mapper1.selectById(1L);
           log.info("SqlSession1查询结果:{}", user1);
           log.info("SqlSession2查询...");
           User user2 = mapper2.selectById(1L);
           log.info("SqlSession2查询结果:{}", user2);
           log.info("两次查询结果是否为同一对象:{}", user1 == user2);
       }
   }
   /**
    * 测试一级缓存失效:执行增删改操作
    */

   @Test
   public void testFirstLevelCacheMissAfterUpdate() {
       try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
           UserMapper mapper = sqlSession.getMapper(UserMapper.class);
           log.info("第一次查询...");
           User user1 = mapper.selectById(1L);
           log.info("第一次查询结果:{}", user1);
           log.info("执行更新操作...");
           User updateUser = new User();
           updateUser.setId(1L);
           updateUser.setEmail("new_email@example.com");
           mapper.updateById(updateUser);
           sqlSession.commit();
           log.info("第二次查询...");
           User user2 = mapper.selectById(1L);
           log.info("第二次查询结果:{}", user2);
           log.info("两次查询结果是否为同一对象:{}", user1 == user2);
       }
   }
   /**
    * 测试一级缓存失效:手动清空缓存
    */

   @Test
   public void testFirstLevelCacheMissAfterClear() {
       try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
           UserMapper mapper = sqlSession.getMapper(UserMapper.class);
           log.info("第一次查询...");
           User user1 = mapper.selectById(1L);
           log.info("第一次查询结果:{}", user1);
           log.info("手动清空一级缓存...");
           sqlSession.clearCache();
           log.info("第二次查询...");
           User user2 = mapper.selectById(1L);
           log.info("第二次查询结果:{}", user2);
           log.info("两次查询结果是否为同一对象:{}", user1 == user2);
       }
   }
}

运行测试类,你会发现:

  1. 同一个SqlSession执行相同查询时,只执行一次SQL,第二次直接从缓存获取,且两次结果是同一对象。
  2. 不同SqlSession、执行增删改操作、手动清空缓存都会导致一级缓存失效。

三、二级缓存的生效机制

3.1 二级缓存的定义与范围

二级缓存是Mapper级别的缓存,默认关闭,需要手动开启。它的作用范围是同一个Mapper接口的namespace,不同的SqlSession可以共享二级缓存。

3.2 二级缓存的配置与开启

3.2.1 全局配置

在application.yml中开启二级缓存:

mybatis-plus:
 configuration:
   cache-enabled: true

3.2.2 Mapper配置

在Mapper接口上添加@CacheNamespace注解:

package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.User;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
* 用户Mapper接口
* @author ken
*/

@Mapper
@CacheNamespace
public interface UserMapper extends BaseMapper<User> {
   /**
    * 根据用户名查询用户
    * @param username 用户名
    * @return 用户信息
    */

   @Select("SELECT * FROM user WHERE username = #{username}")
   User selectByUsername(@Param("username") String username);
}

注意:实体类必须实现Serializable接口,因为二级缓存可能会将数据序列化到磁盘或网络传输。

3.3 二级缓存的执行流程

3.4 二级缓存的事务管理

二级缓存的一个重要特点是:只有当SqlSession提交或关闭时,查询结果才会被写入二级缓存。这是为了避免脏读,确保只有提交后的数据才会被缓存。

3.5 实战示例:二级缓存的生效与失效场景

测试类SecondLevelCacheTest.java:

package com.jam.demo;
import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 二级缓存测试类
* @author ken
*/

@Slf4j
@SpringBootTest
public class SecondLevelCacheTest {
   @Autowired
   private SqlSessionFactory sqlSessionFactory;
   /**
    * 测试二级缓存生效:不同SqlSession,相同查询,SqlSession提交
    */

   @Test
   public void testSecondLevelCacheHit() {
       try (SqlSession sqlSession1 = sqlSessionFactory.openSession();
            SqlSession sqlSession2 = sqlSessionFactory.openSession()) {
           UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
           UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
           log.info("SqlSession1查询...");
           User user1 = mapper1.selectById(1L);
           log.info("SqlSession1查询结果:{}", user1);
           log.info("SqlSession1提交...");
           sqlSession1.commit();
           log.info("SqlSession2查询...");
           User user2 = mapper2.selectById(1L);
           log.info("SqlSession2查询结果:{}", user2);
           log.info("两次查询结果是否为同一对象:{}", user1 == user2);
           log.info("两次查询结果内容是否相同:{}", user1.equals(user2));
       }
   }
   /**
    * 测试二级缓存失效:SqlSession未提交
    */

   @Test
   public void testSecondLevelCacheMissWithoutCommit() {
       try (SqlSession sqlSession1 = sqlSessionFactory.openSession();
            SqlSession sqlSession2 = sqlSessionFactory.openSession()) {
           UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
           UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
           log.info("SqlSession1查询...");
           User user1 = mapper1.selectById(1L);
           log.info("SqlSession1查询结果:{}", user1);
           log.info("SqlSession2查询...");
           User user2 = mapper2.selectById(1L);
           log.info("SqlSession2查询结果:{}", user2);
           log.info("两次查询结果是否为同一对象:{}", user1 == user2);
       }
   }
   /**
    * 测试二级缓存失效:执行增删改操作
    */

   @Test
   public void testSecondLevelCacheMissAfterUpdate() {
       try (SqlSession sqlSession1 = sqlSessionFactory.openSession();
            SqlSession sqlSession2 = sqlSessionFactory.openSession();
            SqlSession sqlSession3 = sqlSessionFactory.openSession()) {
           UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
           UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
           UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
           log.info("SqlSession1查询并提交...");
           User user1 = mapper1.selectById(1L);
           sqlSession1.commit();
           log.info("SqlSession1查询结果:{}", user1);
           log.info("SqlSession2执行更新操作并提交...");
           User updateUser = new User();
           updateUser.setId(1L);
           updateUser.setEmail("second_level_cache@example.com");
           mapper2.updateById(updateUser);
           sqlSession2.commit();
           log.info("SqlSession3查询...");
           User user3 = mapper3.selectById(1L);
           log.info("SqlSession3查询结果:{}", user3);
       }
   }
}

运行测试类,你会发现:

  1. 当第一个SqlSession提交后,第二个SqlSession执行相同查询时,会直接从二级缓存获取结果(注意:两次结果不是同一对象,因为二级缓存会反序列化对象)。
  2. SqlSession未提交、执行增删改操作都会导致二级缓存失效。

四、易混淆点深度对比

4.1 一级缓存vs二级缓存:核心差异一览

对比项 一级缓存 二级缓存
作用范围 SqlSession级别 Mapper namespace级别
默认状态 默认开启 默认关闭
开启方式 无需配置 全局配置+Mapper注解
存储介质 内存(HashMap) 内存(可扩展至磁盘)
失效场景 不同SqlSession、增删改、手动清空 增删改、缓存超时、内存不足
事务要求 无 需SqlSession提交/关闭才写入
序列化要求 无 实体类需实现Serializable

4.2 缓存失效的常见场景总结

  1. 一级缓存失效:
  • 使用不同的SqlSession
  • 执行增删改操作(即使操作的是不同表)
  • 手动调用sqlSession.clearCache()
  • 查询条件不同
  1. 二级缓存失效:
  • 执行增删改操作
  • SqlSession未提交或关闭
  • 缓存超时
  • 内存不足触发LRU策略
  • 查询条件不同

结语

本文深入拆解了MyBatis的三个核心机制:Mapper动态代理、一级缓存和二级缓存。通过底层原理分析、流程图展示和实战代码示例,我们不仅理解了这些机制的工作原理,更掌握了它们在实际开发中的使用方法和注意事项。在实际项目中,我们应该根据业务场景合理使用缓存,避免缓存带来的脏读问题,同时也要注意缓存的性能优化。希望本文能对你有所帮助,让你在MyBatis的使用上更加得心应手。

目录
相关文章
|
10月前
|
Java Shell 测试技术
Jmeter快速入门
本文介绍JMeter的安装与快速入门,包括下载、解压、运行步骤,以及中文语言设置和基本使用方法,帮助用户快速上手性能测试工具JMeter。
|
7月前
|
人工智能 自然语言处理 安全
“养龙虾玩Skill”指南!OpenClaw(Clawdbot)阿里云/本地部署+新手3种 Skill 挖掘AI工具核心价值
OpenClaw(昵称“龙虾”)的爆火,让“养龙虾玩Skill”成了2026年的新潮流——有人用它自动化处理工作,有人靠定制部署服务变现,甚至闲鱼上500元一次的上门部署生意都应运而生。但多数新手入手后,只停留在基础对话层面,殊不知OpenClaw的核心价值藏在Skill生态中——这些可执行的“能力单元”,就像一个个专属机器人,能将自然语言指令转化为实际操作,从文档处理到硬件控制,覆盖全场景需求。
3694 5
|
3月前
|
缓存 人工智能 测试技术
阿里云Qwen3.7-Max全解析:核心能力、免费权益与接入计费完整教程
2026年推出的阿里云千问Qwen3.7-Max是面向智能体时代打造的纯文本旗舰大模型,整体架构专为深度逻辑推理、百万字长文档处理、大型代码工程、数十小时长周期自主Agent任务深度优化,不搭载图像、视频解析能力,聚焦高强度纯文本业务,在科研推演、全仓库代码重构、超长合同审核、自动化办公智能体等场景拥有行业领先表现。依托阿里云百炼大模型服务平台统一对外提供网页对话、API程序化调用、智能体框架对接三类使用渠道,配套分层免费试用政策、限时折扣按量计费、团队包月Token Plan三套资费体系,完整覆盖个人学习、开发者测试、企业规模化生产三类使用人群。本文从核心技术能力、分层免费试用规则、两套商业
1455 1
|
3月前
|
人工智能 缓存 API
阿里云百炼Token Plan坐席如何选择?按Credits计费哪个套餐更划算?算一下就知道了
阿里云百炼TokenPlan含标准版(198元/月,2.5万Credits)、高级版(698元/月,10万Credits)和尊享版(1398元/月,25万Credits)。经测算,尊享版单Credits仅0.0056元,折合百万Tokens约1.12元,显著低于按量计费(2元/百万Tokens),性价比更高。在阿里云百炼官网:https://t.aliyun.com/U/fPVHqY 免费领取千万Tokens
|
4月前
|
人工智能 机器人 语音技术
听懂、接住、说得自然:一通好的智能外呼到底需要什么?
阿里云智能联络中心2.0聚焦真实外呼场景,通过全链路优化实现1.8秒内极速响应、行业级ASR/语义理解、自然打断承接与拟人化语音,解决“听不懂、接不住、说不自然”三大痛点,让AI外呼从“拨出去”真正升级为“聊下去、转化成”。
453 0
|
11月前
|
人工智能 双11 开发者
中小电商卖家福音!用AI 3分钟搞定高转化主图+详情页!
还在为电商设计成本高、效率低发愁?阿里云百炼全新推出「一键生成电商组图」功能,AI智能生成主图、详情页等视觉素材,零设计基础也能快速出图!限时参与活动,赢定制好礼,更有超值Tokens优惠包,助力爆款打造!
|
人工智能 监控 Java
零代码改造 + 全链路追踪!Spring AI 最新可观测性详细解读
Spring AI Alibaba 通过集成 OpenTelemetry 实现可观测性,支持框架原生和无侵入探针两种方式。原生方案依赖 Micrometer 自动埋点,适用于快速接入;无侵入探针基于 LoongSuite 商业版,无需修改代码即可采集标准 OTLP 数据,解决了原生方案扩展性差、调用链易断链等问题。未来将开源无侵入探针方案,整合至 AgentScope Studio,并进一步增强多 Agent 场景下的观测能力。
3501 109
|
JSON 前端开发 Java
深入理解 Spring Boot 中日期时间格式化:@DateTimeFormat 与 @JsonFormat 完整实践
在 Spring Boot 开发中,日期时间格式化是前后端交互的常见痛点。本文详细解析了 **@DateTimeFormat** 和 **@JsonFormat** 两个注解的用法,分别用于将前端传入的字符串解析为 Java 时间对象,以及将时间对象序列化为指定格式返回给前端。通过完整示例代码,展示了从数据接收、业务处理到结果返回的全流程,并总结了解决时区问题和全局配置的最佳实践,助你高效处理日期时间需求。
2340 0
|
存储 安全 API
Typora介绍、安装、图床设置和使用
Typora是一款简洁高效的Markdown编辑器,支持实时预览、数学公式编辑、表格制作与多格式导出,适用于写作、学术与技术文档。本文详细介绍其安装、激活、主题设置、图床配置(PicGo+SM.MS/COS/OSS)及常用功能使用,助你高效搭建写作环境。
2519 3
Typora介绍、安装、图床设置和使用

热门文章

最新文章